CF600E——DSU on tree

终于又回来了,我的上一篇博客距今已经13天了。最近忙于期末考试等事,居然有一个星期没摸过键盘。
好了不闲扯了,开始我们今天的新算法:dsu on tree


题目传送门
这题的大意就是给你一棵树,每个点有一种颜色,求每个点的子树中出现次数最多的颜色(如果两种颜色出现次数相同,那就输出它们的和)。
这道题的解法是dsu on tree,翻译成中文应该是树上启发式合并。然而这种算法就是十足的暴力
我们来看一下这个算法的流程:
1.基于树链剖分,将树划分为轻重链,将点划分为轻重点。
2.递归统计答案。
我们先基于暴力的思路:对于每个点,都用一个桶暴力统计它的子树,记下答案,然后将桶清空。
我们会发现,这样暴力的统计其实有大量的重复清空和重复统计,正是这样的重复,导致算法的复杂度达到 O(N2) O ( N 2 ) ,那么我们怎样合并可以减少这种冗余的操作呢?我们基于轻重链剖分的思想。对于点x统计答案时,我们就先考虑统计它儿子的答案:(在暴力的时候统计完任何一个儿子的答案都要在桶中进行删除)对于x的轻儿子,我们将其的删除。而对于重儿子,我们在数组里进行保留。
基于这样的操作,算法的复杂度就可以优化到 O(Nlog2N) O ( N l o g 2 N ) 。因为这样做以后,我们会发现在统计每个点答案时只会去统计它的轻儿子的。而轻儿子也只会统计轻儿子的……根据树链剖分的原理,每个点最多只会被统计 logN l o g N 次,所以算法的复杂度就为 O(Nlog2N) O ( N l o g 2 N )
#include
#define MAXN 100005
#define ll long long
using namespace std;
ll read(){
    char c;ll x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
ll n,cnt,w[MAXN],head[MAXN<<1],nxt[MAXN<<1],go[MAXN<<1],mx,sum;
ll fa[MAXN],son[MAXN],siz[MAXN],dep[MAXN],ans[MAXN],vis[MAXN],bucket[MAXN];
void add(int x,int y){
    go[cnt]=y;nxt[cnt]=head[x];head[x]=cnt;cnt++;
    go[cnt]=x;nxt[cnt]=head[y];head[y]=cnt;cnt++;
}
void dfsI(int x,int father){
    fa[x]=father;dep[x]=dep[fa[x]]+1;
    siz[x]=1;son[x]=-1;
    for(int i=head[x];i!=-1;i=nxt[i]){
        int to=go[i];
        if(to==fa[x]) continue;
        dfsI(to,x);siz[x]+=siz[to];
        if(siz[to]>siz[son[x]]) son[x]=to;
    }
}
void rise(int x,int ad){
    if(!vis[x])bucket[w[x]]+=ad;
    if(ad>0&&bucket[w[x]]==mx) sum+=w[x];
    if(ad>0&&bucket[w[x]]>mx){
        mx=bucket[w[x]];sum=w[x];
    }
    for(int i=head[x];i!=-1;i=nxt[i]){
        int to=go[i];
        if(to==fa[x]||vis[to]) continue;
        rise(to,ad);
    }
}
void dfsII(int x,int fl){
    for(int i=head[x];i!=-1;i=nxt[i]){
        int to=go[i];
        if(to==fa[x]||to==son[x]) continue;
        dfsII(to,0);
    }
    if(son[x]!=-1) dfsII(son[x],1),vis[son[x]]=1;
    rise(x,1);ans[x]=sum;
    if(son[x]!=-1) vis[son[x]]=0;
    if(!fl) rise(x,-1),mx=sum=0;
}
int main()
{
    n=read();register int i;
    memset(head,-1,sizeof(head));
    for(i=1;i<=n;i++) w[i]=read();
    for(i=1;iint x=read(),y=read();
        add(x,y);
    }
    dfsI(1,0);
    dfsII(1,0);
    for(i=1;i<=n;i++) printf("%I64d ",ans[i]);
    return 0;
}

你可能感兴趣的:(codeforces)