首先要考虑入手点。先考虑一个一个玩家处理,显然不加优化的话,时间复杂度是O(n)的。发现对于玩家路径上的点都有一个观察员,一个都不能忽视,看起来是很难优化了。在做题时,发现一个思路很难想,就应该考虑一下换个角度思考。OI中如此,生活亦是如此。
那就尝试从观察员入手。对于每个玩家,他们在树上的路径一定能分为向上和向下的两段(分的段长度可以为0),设当前玩家x的路径的起点为st,终点为ed,st和ed的LCA为ca,路径长度为dis,当观察员u在玩家x向上走的那一段时,若玩家能在Wu秒时正好被观察员看到,那么一定有dep st-W u=dep u;当观察员u在玩家x从ca向ed走(即向下走)的那一段时,若玩家能在Wu秒时正好被观察员看到,那么一定有dep ed-dep u=dis-W u。因为我们要从观察员入手,所以我们将上面的两个式子中与u有关的移项到左边,无关的移项到右边,就得到了两个式子:1式:dep u+W u=dep st;2式:dep u+ W u=dis-dep ed。其实到这里就有一个显明的思路了:我们枚举每个观察员u,看一下有多少经过u的路径的dep st 等于dep u+W u、有多少经过u的路径的dis-dep ed等于dep u+ W u。这两个“多少”的和去重后(消去同一条路径同时满足两个条件从而计算2次带来的影响)就是观察员u能观察到的玩家数(答案)。为什么呢?其实若该路径的dep st 等于dep u+W u,那么该路径的st肯定不能比u还浅,那u肯定在这个路径向上走的半段;若该路径的dis-dep ed等于dep u+ W u,即dep ed-dep u=dis-W u,若u在该路径向上走的半段,这个式子显然不会成立(dis-Wu就是玩家走到u后再走到ed需要的时间,当u在路径的下半段时,dep ed-dep u正好就是从u走到ed所需的时间,若u在路径的上半段,dep ed-dep u只会比所需时间小),所以u只能在该路径向下走的另半段。
考虑怎么寻找答案,显然对每个u都暴力一遍所有的路径是不行的了。经过进一步的思考,发现若一条路径经过了u,那么它的起点st和终点ed至少有一个会在u的子树立,若st在u的子树里,那么u就在路径向上的半段;若ed在u的子树里,那么u就在路径向下的半段;特殊地,若st和ed都在u的子树里,那么u同时在路径向上和向下的半段,即u是这条路径的lca,此时应注意上文的去重。而对于一条路径,它可能会对多个u产生答案的贡献,但每次产生贡献时都有一个共同点,就是这条路径的depst 或 dis - dep ed 产生了一次相应的相等。由此可以考虑建2个全局的桶,分别记录dep st等于桶下标值的路径条数和 dis - dep ed 等于下标值的路径条数。但是对于dis - dep ed,发现它可能小于0,为了防止第二个桶的下溢出,第二个桶维护信息的意义应改为dis - dep ed+N 等于下标值的路径条数(N是一个较大的正整数,只要保证dis - dep ed+N恒非负,N可以在空间足够的情况下随便取)。对于每个观察员u,直接去2个桶里相应下标处找答案就行了。
但这里有一个问题,即桶里记录的路径条数所包含的路径中,有可能有路径是不经过u的。这好办,可以从根dfs,当dfs到某个路径的起点时入一下第一个桶、终点时入一下第二个桶,等到回溯到这条路径的最高点(即这条路径的ca)时让先前入的桶的相应位置处分别--(出桶)就好了。
还有一个问题,即如果只通过上面的操作的话,还是可能会碰到桶里还记录着这条路径,但这条路径仍没经过u,这时只有一种可能,就是这条路径的起点或终点在u的兄弟的子树中,但ca在u的上面。u的答案应该是遍历完以u为根的子树后桶内的相应下标处的增量,即遍历完以u为根的子树后,2个桶会有一些下标处的值发生了变化,这些变化正是以u为根的子树对桶的影响,而只有这些影响量才可能是u的答案,因为这些影响量其实就是在u的子树中的路径起点或终点入桶、ca在u的子树中的路径出桶造成的结果,别忘了只有路径起点或终点在u的子树中,且ca为u或是u的祖先的路径才能对u产生贡献。而这些影响量,就可以通过记录桶的增量来记录。
代码实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 5 #define swap(x,y) (x^=y,y^=x,x^=y) 6 7 using namespace std; 8 9 const int N=300005,M=300005; 10 11 int n,m,w[N],son[N],top[N],siz[N],f[N],lst[N]; 12 int nxt[N<<1],to[N<<1],cnt,dep[N],dis[M],ans[N]; 13 int t1[N],t2[N<<1],st[N]; 14 15 char ch; 16 17 vector<int> lcau[N],lenv[N]; 18 19 struct node{ 20 int num,ord; 21 }; 22 23 vector<node> lcav[N]; 24 25 inline int read() 26 { 27 int x=0; 28 ch=getchar(); 29 while(!isdigit(ch)) ch=getchar(); 30 while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); 31 return x; 32 } 33 34 inline void addedge(int u,int v) 35 { 36 nxt[++cnt]=lst[u]; 37 lst[u]=cnt; 38 to[cnt]=v; 39 } 40 41 void dfs1(int u,int fa)//处理f,dep,son,siz 树剖预处理第一遍dfs 42 { 43 siz[u]=1; 44 dep[u]=dep[fa]+1; 45 int t; 46 for(int e=lst[u];e;e=nxt[e]) 47 if((t=to[e])!=fa) 48 { 49 f[t]=u; 50 dfs1(t,u); 51 if(siz[t]>siz[son[u]]) 52 son[u]=t; 53 siz[u]+=siz[t]; 54 } 55 } 56 57 void dfs2(int u,int bos)//处理top 树剖预处理第二遍dfs 58 { 59 top[u]=bos; 60 if(son[u]) 61 { 62 dfs2(son[u],bos); 63 int t; 64 for(int e=lst[u];e;e=nxt[e]) 65 { 66 if((t=to[e])!=son[u]&&t!=f[u]) 67 dfs2(t,t); 68 } 69 } 70 } 71 72 inline int lca(int x,int y)//树剖求LCA过程 73 { 74 while(top[x]!=top[y]) 75 { 76 if(dep[top[x]]>dep[top[y]]) 77 x=f[top[x]]; 78 else 79 y=f[top[y]]; 80 } 81 return dep[x]>dep[y]?y:x; 82 } 83 84 void dfs(int u)//找答案的dfs 85 { 86 int aft=t1[dep[u]+w[u]]+t2[w[u]-dep[u]+N];//记录原来桶的量 87 for(int e=lst[u];e;e=nxt[e]) 88 { 89 if(to[e]!=f[u]) 90 dfs(to[e]); 91 } 92 if(st[u])//入第一个桶 93 { 94 t1[dep[u]]+=st[u]; 95 } 96 if(lenv[u].size())//入第二个桶 97 { 98 int l=lenv[u].size(),t; 99 for(int i=0;i<l;++i) 100 { 101 t=lenv[u][i]; 102 t2[t-dep[u]+N]++; 103 } 104 } 105 ans[u]=t1[dep[u]+w[u]]+t2[w[u]-dep[u]+N]-aft;//用当前桶的量减原来桶的量得到增量,得当前点u的答案 106 if(lcau[u].size())//回溯到u的父亲后以u为ca的路径就没用了,要出桶 107 { 108 int l=lcau[u].size(),x,y; 109 for(int i=0;i<l;++i) 110 { 111 x=lcau[u][i]; 112 y=lcav[u][i].num; 113 if(dep[u]+w[u]==dep[x]&&w[u]-dep[u]==dis[lcav[u][i].ord]-dep[y])//去重 114 ans[u]--; 115 t1[dep[x]]--; 116 t2[dis[lcav[u][i].ord]-dep[y]+N]--; 117 } 118 } 119 } 120 121 int main() 122 { 123 n=read(),m=read(); 124 int u,v; 125 for(int i=1;i<n;++i) 126 { 127 u=read(),v=read(); 128 addedge(u,v); 129 addedge(v,u); 130 } 131 for(int i=1;i<=n;++i) 132 w[i]=read(); 133 dfs1(1,0);dfs2(1,1); 134 int ff,l; 135 for(int i=1;i<=m;++i) 136 { 137 u=read(),v=read(); 138 ff=lca(u,v);//树链剖分求LCA 139 st[u]++;//记录以u为路径起点的路径条数 140 dis[i]=l=dep[u]+dep[v]-(dep[ff]<<1); 141 lcau[ff].push_back(u);//记录以ff为ca的路径的起点 142 lcav[ff].push_back((node){v,i});//记录以ff为ca的路径的终点和编号 143 lenv[v].push_back(l);//记录以v为路径的终点的路径长度 144 } 145 dfs(1); 146 for(int i=1;i<=n;++i) 147 { 148 printf("%d ",ans[i]); 149 } 150 return 0; 151 }
总结:
对于产生全局答案贡献的处理方式:建全局桶。
若想从一个存储结构中得到答案,应时刻维护结构以保证其存储的数据的正确性。
不写出式子/画出图来,难以更深入地优化或思考。
感觉这道题跟差分的关系不大。只不过修改的复杂度与差分的区间修改都是O(1),大部分题目的修改复杂度很难有O(1),故会觉得这道题与差分有点像吧。