dsu on tree(树上启发式合并)简介(codeforces 600 E)

推荐这篇文章,这里只是简介

算法用途

主要解决像 统计树上一个节点的子树中具有某种特征的节点数 这种问题。

算法实现

我们以codeforces 600 E为例

暴力

对于这种问题,最简单的方法就是每次暴力统计其子树,然后再暴力删去数据。复杂度 O(n2)
很明显,暴力跑得慢的原因就是它多删、统计了很多次。

平衡树启发式合并

每个节点建一棵平衡树,每次保留最大的子树,把小的子树合并到大的上去。复杂度 O(nlog2n) ,可以卡过这道题。

dsu on tree(轻重链剖分)

暴力消去轻儿子的影响,每次保留重儿子。根据树剖的性质可以知道每次复杂度是 O(logn) 的,因此总复杂度为 O(nlogn)

代码:

#include
#include
#include
#include
#define N 100005
using namespace std;
typedef long long LL;
struct edge{
    int next,to;
}ed[N<<1];
int n,m,k,co,fa[N],sz[N],to[N],c[N],h[N],num[N];
//fa[],sz[],to[]是树剖的各种量,h[]为邻接表,c[]为颜色,num[]为该颜色出现的次数
LL ans[N],sum;
bool v[N];
inline char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; return *l++;
}
inline int _read(){
    int x=0; char ch=readc();
    while (!isdigit(ch)) ch=readc();
    while(isdigit(ch)) x=x*10+ch-48,ch=readc();
    return x;
}
void addedge(int x,int y){
    ed[++k].next=h[x],ed[k].to=y,h[x]=k;
}
void dfs1(int x){//第一次DFS把重儿子求出来
    sz[x]=1;
    for (int i=h[x];i;i=ed[i].next)
        if (ed[i].to!=fa[x]){
            int v=ed[i].to;
            fa[v]=x,dfs1(v),sz[x]+=sz[v];
            if (sz[to[x]]void nsrt(int x,int f){//统计并修改
    num[c[x]]+=f;
    if (f>0&&num[c[x]]>=co){
        //如果当前颜色次数大于等于现在的颜色
        //这道题等于的也要统计进去
        if (num[c[x]]>co) sum=0,co=num[c[x]];//如果比当前的大了需要重新计算
        sum+=c[x];
    }
    for (int i=h[x];i;i=ed[i].next)
        if (ed[i].to!=fa[x]&&!v[ed[i].to])
            nsrt(ed[i].to,f);
}
void dfs2(int x,bool f){
    //第二次DFS统计答案
    //f表示这次统计是否保留
    for (int i=h[x];i;i=ed[i].next)
        if (ed[i].to!=fa[x]&&ed[i].to!=to[x])
            dfs2(ed[i].to,0);//优先处理轻儿子
    if (to[x]) dfs2(to[x],1),v[to[x]]=1;
    //接着处理重儿子,v[]表示是否需要统计
    nsrt(x,1),ans[x]=sum;//统计并修改影响
    if (to[x]) v[to[x]]=0;
    if (!f) nsrt(x,-1),co=sum=0;//不保留要改回来
}
int main(){
    n=_read();
    for (int i=1;i<=n;i++) c[i]=_read();
    for (int i=1;iint u=_read(),v=_read();
        addedge(u,v),addedge(v,u);
    }
    dfs1(1),dfs2(1,0);
    for (int i=1;i<=n;i++) printf("%I64d ",ans[i]);
    return 0;
}

你可能感兴趣的:(其他网站,数据结构---其他树相关,算法/总结/游记,蒟蒻zxl的Blog专栏,codeforces,树上启发式合并)