CF#600E Lomsat gelral---DSU on tree


(https://codeforces.com/contest/600/problem/E)

题目描述

给一棵以\(1\)为根树,每个节点有一个颜色\(c_i\),求每个点的子树中颜色出现次数最多的颜色编号和。
\(1\leq n\leq 10^5,1\leq c_i\leq n\)

sol:

\(DSU\)就是这样一个处理子树内某些问题的工具,但不支持修改操作。首先需要重链剖分,接下来每次只要把轻儿子的贡献暴力往重儿子里并即可。但如果仔细想想就会发现,其实算重了轻儿子的贡献,于是一定要记住在算完一个节点的答案后要把轻儿子的影响去掉。
但这个时间复杂度为什么是对的?
首先考虑一个结论,从任一点到根的路径上轻边条数不会超过\(\log n\)条,因为显然轻儿子的大小不会超过整个子树的一半。利用这个结论,在极端情况,也就是一条路径上轻边条数恰为\(\log n\),那在每次统计轻边时都会遍历轻子树,故复杂度为\(O(\log n)\)再乘上遍历的复杂度。下面考虑遍历,每个节点只被访问一次,要么是它的祖先节点暴力统计轻儿子或消除影响时,要么是它自己统计答案时,复杂度\(O(n)\)。故总复杂度\(O(n\log n)\)。(带口胡成分)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
    int x=0;char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return x;
}
const int N=100005;
int n,p[N],ban,h[N],maxn;
ll sum,ans[N];
int ver[N<<1],nxt[N<<1],head[N],tot;
int fa[N],son[N],siz[N];
inline void link(int x,int y){ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
void dfs1(int x,int la){
    fa[x]=la;siz[x]=1;
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==la) continue;
        dfs1(y,x);
        siz[x]+=siz[y];
        if(siz[y]>siz[son[x]]) son[x]=y;
    }
}
void update(int x,int val){
    h[p[x]]+=val;
    if(h[p[x]]>maxn) maxn=h[p[x]],sum=p[x];
    else if(h[p[x]]==maxn) sum+=p[x];
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa[x]||y==ban) continue;
        update(y,val);//每次都需要在整棵子树内删除某个点的贡献 
    }
}
void dfs2(int x,int opt){
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa[x]||y==son[x]) continue;
        dfs2(y,0);//暴力统计轻边的贡献,opt = 0表示递归完成后消除对该点的影响 
    }
    if(son[x]) dfs2(son[x],1);//统计重儿子的贡献,不消除影响 
    ban=son[x];update(x,1);//暴力统计所有轻儿子的贡献 
    ans[x]=sum;ban=0;
    if(!opt) update(x,-1),maxn=sum=0;//如果需要删除贡献的话就删掉 
}
int main(){
    n=read();
    for(int i=1;i<=n;i++) p[i]=read();
    for(int i=1;i<n;i++){
        int x=read(),y=read();
        link(x,y);link(y,x);
    }
    dfs1(1,0);dfs2(1,1);
    for(int i=1;i<=n;i++)
        printf("%I64d ",ans[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zxynothing/p/11627099.html