NOIP 史上最毒瘤的一个题目......花了 2h 总算肝出来了。
<h2>解题报告</h2>
<h3>暴力测试点</h3>
大力枚举时间然后统计答案就可以了…….简单的模拟题目。
期望得分 $25$ 分。
<h3>链情况</h3>
考虑链情况的每一个人只能向左或向右走,也就是 $s \leq t$ 向右,否则向左。我们考虑点 $v$ 的观察员,不妨发现他能观察到的向右的玩家应满足 $ s + w_v = v $ ,即 $ s=v-w_v $。
同理,对于向左走的玩家有 $ s=v+w_v $。
排序或者用桶来维护这个东西即可。
时间复杂度 $ O(nlogn) \to O(n) $。
期望得分 $40$ 分。
<h3>起点为1</h3>
我们钦定这个树的根是1,找一找规律 发现点 $v$ 的观察员能够观察到的点当且仅当 $w_v= depth_v$,统计每个点的子树有多少个终点即可。
期望得分 $60$ 分。
<h3>终点为1</h3>
依然钦定,考虑一个点 $v$ 能看到的路径,显然该路径的起点应该在 $j$ 的子树里而且满足条件 $w_v + depth_v = l$,其中 $l$ 为链的长度。这个我们开一个全局的桶,每一次我们 $dfs$ 后统计桶的增量即可。
期望得分 $80$ 分。
<h3>无特殊情况</h3>
你都80分了还不满足过分了啊
依然钦定,总体考虑 $i$ 点能看到谁。
把上面两种情况整合一下:发现对于一条路径 $u\to v$,相当于可以拆成两条路径 $u\to lca$ 和 $lca\to v$。这样的话对于向上走的玩家 $i$ 点能看见应满足 $depth_u - w_i = depth_i$,即 $ depth_u = w_i + depth_i $。
同理:向下的路径满足的条件是 $ depth_v -depth_i = l - w_i $,其中 $l$ 是链长,移项也可以得到 $ depth_v - len = depth_i - w_i $。
对着两种路径进行差分(我觉得不像是差分),我们用一个桶来统计,下标表示等式右边的表达式,记录的值是左边的表达式,如果出现负数,整体左移即可(对增量没有影响)。
我们来考虑 $lca$ 会被重复计算,那么我们减掉 $lca$ 的一次贡献即可。
具体可看代码,
<h2>代码实现</h2>
#include <algorithm> #include <iostream> #include <cstring> #include <climits> #include <cstdio> #include <vector> #include <cstdlib> #include <ctime> #include <cmath> #include <queue> #include <stack> #include <map> #include <set> #define Re register #define LL long long #define U unsigned #define FOR(i,a,b) for(Re int i = a;i <= b;++i) #define RFOR(i,a,b) for(Re int i = a;i >= b;--i) #define SFOR(i,a,b,c) for(Re int i = a;i <= b;i+=c) #define CLR(i,a) memset(i,a,sizeof(i)) #define BR printf("--------------------n") #define DEBUG(x) std::cerr << #x << '=' << x << std::endl namespace fastIO{ #define BUF_SIZE 100000 #define OUT_SIZE 100000 #define ll long long bool IOerror=0; inline char nc(){ static char buf[BUF_SIZE],p1=buf+BUF_SIZE,pend=buf+BUF_SIZE; if (p1==pend){ p1=buf; pend=buf+fread(buf,1,BUF_SIZE,stdin); if (pend==p1){IOerror=1;return -1;} } return *p1++; } inline bool blank(char ch){return ch==' '||ch=='n'||ch=='r'||ch=='t';} inline void read(int &x){ bool sign=0; char ch=nc(); x=0; for (;blank(ch);ch=nc()); if (IOerror)return; if (ch=='-')sign=1,ch=nc(); for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0'; if (sign)x=-x; } inline void read(ll &x){ bool sign=0; char ch=nc(); x=0; for (;blank(ch);ch=nc()); if (IOerror)return; if (ch=='-')sign=1,ch=nc(); for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0'; if (sign)x=-x; } #undef ll #undef OUT_SIZE #undef BUF_SIZE }; using namespace fastIO; const int MAXN = 300000 + 5; struct Node{ struct Edge *firstEdge; int depth,ts,num; // ts = 题目中的 w bool vis; }node[MAXN]; struct Edge{ Node s,t; Edge *next; }pool[MAXN2],frog = pool; struct Person{ int s,t; int lca,len; }p[MAXN]; Edge New(Node s,Node *t){ Edge *ret = ++frog; ret->s = s;ret->t = t; ret->next = s->firstEdge; return ret; } inline void add(int u,int v){ node[u].firstEdge = New(&node[u],&node[v]); node[v].firstEdge = New(&node[v],&node[u]); } int N,M; int fMAXN; float log2N; inline void init(){ CLR(f,-1); FOR(i,1,N){ node[i].num = i; node[i].depth = 0; node[i].vis = false; } log2N = log2(N); node[1].depth = 1; node[1].vis = true; } void dfs(Node *v){ #define EFOR(i,v) for(Edge *i = v->firstEdge;i;i = i->next) FOR(i,1,log2N){ if((1 << i) >= v->depth) break; fv->num = ff[v->num][i-1]; } EFOR(e,v){ if(!e->t->vis){ e->t->vis = true; fe->t->num = v->num; e->t->depth = v->depth + 1; dfs(e->t); } } #undef EFOR } inline int lca(int x,int y){ int dx = node[x].depth,dy = node[y].depth; if(dx != dy){ if(dx < dy){ std::swap(x,y); std::swap(dx,dy); } int d = dx - dy; FOR(i,0,log2N){ if((1 << i) & d) x = fx; } } if(x == y) return x; RFOR(i,log2N,0){ // if(nodef[x].depth < 0) continue; if(fx == fy) continue; else x = fx,y = fy; } return fx; } std::vector<int> cf[MAXN],cf1[MAXN],cf2[MAXN]; int max,t[MAXN],ans[MAXN],cnt[MAXN]; // cf: lca 为 i 的人起点深度 void dfs1(Node *v){ #define EFOR(i,v) for(Edge *i = v->firstEdge;i;i = i->next) int now = v->depth + v->ts,pre; // now 记录右边,t 记录左边,pre 记录 if(now <= max) pre = t[now]; EFOR(e,v) if(v->depth < e->t->depth) dfs1(e->t); t[v->depth] += cnt[v->num]; if(now <= max) ans[v->num] += t[now]-pre; int sz = cf[v->num].size()-1; RFOR(i,sz,0) tcf[v->num]--; #undef EFOR } void dfs2(Node *v){ #define EFOR(i,v) for(Edge *i = v->firstEdge;i;i = i->next) int now = v->depth - v->ts + MAXN,pre; // 同上 pre = t[now]; EFOR(e,v) if(v->depth < e->t->depth) dfs2(e->t); int sz = cf1[v->num].size()-1; RFOR(i,sz,0) ++tcf1[v->num+MAXN]; sz = cf2[v->num].size()-1; ans[v->num] += t[now] - pre; RFOR(i,sz,0) --tcf2[v->num+MAXN]; // DEBUG(ans[v->num]); #undef EFOR } int main(){ read(N);read(M); FOR(i,1,N-1){ int u,v;read(u);read(v); add(u,v); } init(); dfs(&node[1]); FOR(i,1,N){ read(node[i].ts); max = std::max(max,node[i].depth); } // DEBUG(max); CLR(t,0);CLR(cnt,0); // puts("---------------------------"); FOR(i,1,M){ read(p[i].s);read(p[i].t); // node[p[i].s].cnt++; cnt[p[i].s]++; p[i].lca = lca(p[i].s,p[i].t); p[i].len = node[p[i].s].depth + node[p[i].t].depth - 2*node[p[i].lca].depth; cf[p[i].lca].push_back(node[p[i].s].depth); // 记录 lca 为 i 的人的深度 // printf("%d %dn",p[i].lca,p[i].len); } dfs1(&node[1]); // 处理向上的路径 CLR(t,0); FOR(i,1,M){ int v = node[p[i].t].depth - p[i].len; cf1[p[i].t].push_back(v); // 记录终点为 i 的人的等式左边 cf2[p[i].lca].push_back(v); // 记录 lca 为 i 的人的等式左边 } dfs2(&node[1]); FOR(i,1,M) if(node[p[i].s].depth - node[p[i].lca].depth == node[p[i].lca].ts) --ans[p[i].lca]; FOR(i,1,N) printf("%d%c",ans[i],(i == N) ? 'n' : ' '); return 0; }
2 comments
orz 只有我一个垃圾神仙不会这题的差分做法
Orz 还有 NOIP ++rp