CF600E Lomsat gelral
题意
给你一棵有\(n\)个点的树 $ n \leq 10^5$,树上每个节点都有一种颜色 \(a_i\),让你求每个点子树出现最多的颜色的编号(多个就求和)
思路
考虑暴力怎么写:遍历每个节点—把子树中的所有颜色暴力统计出来算答案—清空—继续递归
这肯定是\(O(n^2)\)的。
\(dsu \space on \space tree\)巧妙的利用了轻重链的性质,把复杂度降到了\(O(nlogn)\)
\(dsu \space on \space tree\)的算法流程是这样的:
对于节点\(i\):
- 遍历每一个节点
- 递归解决所有的轻儿子,同时消除递归产生的影响
- 递归重儿子,不消除递归的影响
- 统计所有轻儿子对答案的影响
- 更新该节点的答案
- 删除所有轻儿子对答案的影响
这样子重儿子的子树就避免了重复操作
至于复杂度的证明,就算了吧
#include <bits/stdc++.h>
const int N=100005;
int edge,to[N<<1],last[N],Next[N<<1],size[N],son[N],Mx,cnt[N],a[N],n,x,y,Son;
long long Ans,ans[N];
void add(int x,int y){
to[++edge]=y;
Next[edge]=last[x];
last[x]=edge;
}
void dfs(int x,int fa){
size[x]=1;
for (int i=last[x];i;i=Next[i])
if (to[i]!=fa){
dfs(to[i],x);
if (size[to[i]]>size[son[x]]) son[x]=to[i];
size[x]+=size[to[i]];
}
}
void add(int x,int fa,int opt){
cnt[a[x]]+=opt;
if (cnt[a[x]]>Mx) Mx=cnt[a[x]],Ans=a[x];
else if (cnt[a[x]]==Mx) Ans+=a[x];
for (int i=last[x];i;i=Next[i])
if (to[i]!=fa && to[i]!=Son)
add(to[i],x,opt);
}
void dfs2(int x,int fa,int opt){
for (int i=last[x];i;i=Next[i])
if (to[i]!=fa){
if (to[i]!=son[x]) dfs2(to[i],x,0);
}
if (son[x]) dfs2(son[x],x,1),Son=son[x];
add(x,fa,1);
Son=0;
ans[x]=Ans;
if (!opt) add(x,fa,-1),Mx=0,Ans=0;
}
int main(){
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
dfs(1,0);
dfs2(1,0,1);
for (int i=1;i<=n;i++) printf("%I64d ",ans[i]);
}
注意
编号和会爆\(int\)
* 生而自由 爱而无畏 *