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\)

上一篇:洛谷 P1082 [NOIP2012 提高组] 同余方程(exgcd)


下一篇:「题解」 [CF600E] Lomsat gelral