zoukankan      html  css  js  c++  java
  • 【学习笔记】树论—Dsu on tree

    【学习笔记】树论—Dsu on tree

    【前言】

    树上启发式合并 ( ext{Dsu on tree}) 一般用来解决一类不带修的子树查询问题(据说这玩意儿原名 静态链分治?)。

    其核心思想为:利用重链剖分的性质优化子树贡献的计算。

    由于 ( ext{zwfymqz}) 巨佬一讲得比较简略,一开始我并没有理解到其精髓,导致一度怀疑其时间复杂度的合理性,甚至连代码都看不懂。
    后来看了看 ( ext{YudeS}) 神仙的另一种写法 自己手玩了几棵树就懂了。
    个人认为后者更好理解,因此本文讲解后者,可能与网上大多数写法略有不同。

    【算法实现】

    先看板题:( ext{Lomsat gelral [CF600E]})

    题目描述:给出一棵 (n) 个结点的树,每个结点都有一种颜色编号,求该树中每棵子树里的出现次数最多的颜色的编号和。

    算法流程如下:

    • 跑一遍 (dfs) 预处理出每个点的重儿子 (son)

    • 对于一个点 (x),首先遍历计算他所有轻儿子的 (ans),并且每算完一个儿子就要清除它的所有贡献。
      接下来计算重儿子 (son[x]) 的答案,并保留 (subtree(son[x])) 中所有点的贡献,然后再暴力加入 (subtree(x)) 中除 (subtree(son[x])) 以外的所有点的贡献,此时即可得到 (ans[x])
      随后回溯到 (fa[x]),如果 (x)(fa[x]) 的轻儿子,那么将在 (x) 这一波计算下来的贡献全部清除,反之则全部保留。

    【时间复杂度】

    经过多次不靠谱的手玩尝试,我们可以发现:每一个点只会在其到根路径中若干重链交界处被统计(也即是从该点到根路径上的轻边数量)。

    而由于重链剖分的性质,任意一个点往上跳时所经过的重链数量不超过 (O(logn)),所以重链交界处(轻边)的数量也不会超过 (O(logn)),因此每个点的统计次数也为 (O(logn))

    总复杂度为:(O(nlogn)),并带上一个通常为 (O(1)) 的常数(统计一个点贡献的复杂度)。

    另外,还有一种与 ( ext{DSU on Tree}) 相似的做法,计算贡献是一样的,核心操作换成了 (dfs) 序+分治,具体见:【题解】( ext{Lomsat gelral [CF600E]})

    【Code】

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #define LL long long
    #define Re register int
    using namespace std;
    const int N=1e5+3;
    int o,n,m,x,y,t,K,tmp,A[N],Q[N],cnt[N],son[N],size[N],head[N];LL ans,Ans[N];
    struct QAQ{int to,next;}a[N<<1];
    inline void add(Re x,Re y){a[++o].to=y,a[o].next=head[x],head[x]=o;}
    inline void in(Re &x){
        int f=0;x=0;char c=getchar();
        while(c<'0'||c>'9')f|=c=='-',c=getchar();
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=f?-x:x;
    }
    inline void dfs(Re x,Re fa){//预处理重儿子
        size[x]=1;
        for(Re i=head[x],to;i;i=a[i].next)
            if((to=a[i].to)!=fa){
                dfs(to,x),size[x]+=size[to];
                if(size[to]>size[son[x]])son[x]=to;
            }
    }
    inline void CL(){//清空贡献,从zero开始
        while(t)cnt[Q[t--]]=0;ans=tmp=0;
    }
    inline void insert(Re x){//加入点x的贡献
        ++cnt[Q[++t]=A[x]];
        if(cnt[A[x]]>tmp)tmp=cnt[ans=A[x]];
        else if(cnt[A[x]]==tmp)ans+=A[x];
    }
    inline void addson(Re x,Re fa){//加入subtree(x)的贡献(以x为根的整棵子树)
        insert(x);
        for(Re i=head[x],to;i;i=a[i].next)
            if((to=a[i].to)!=fa)addson(to,x);
    }
    inline void sakura(Re x,Re fa){
        for(Re i=head[x],to;i;i=a[i].next)
            if((to=a[i].to)!=fa&&to!=son[x])sakura(to,x),CL();//计算轻儿子的答案并清空贡献
        if(son[x])sakura(son[x],x);//计算重儿子的答案并保留subtree(son[x])(以son[x]为根的整棵子树贡献)
        for(Re i=head[x],to;i;i=a[i].next)
            if((to=a[i].to)!=fa&&to!=son[x])addson(to,x);//加入subtree(x)-subtree(son[x])-x(以x的所有轻儿子为根的子树贡献)
        insert(x),Ans[x]=ans;//注意还要把x的贡献也加进去
    }
    int main(){
    //  freopen("123.txt","r",stdin);
        in(n),m=n-1;
        for(Re i=1;i<=n;++i)in(A[i]);
        while(m--)in(x),in(y),add(x,y),add(y,x);
        dfs(1,0),sakura(1,0);
        for(Re i=1;i<=n;++i)printf("%lld ",Ans[i]);
    }
    

    【参考资料】

  • 相关阅读:
    远程监控JVM
    性能测试的思考
    吴恩达《机器学习》课程总结(11)机器学习系统的设计
    吴恩达《机器学习》课程总结(10)应用机器学习的建议
    吴恩达《机器学习》课程总结(9)神经网络的学习
    吴恩达《机器学习》课程总结(8)神经网络表述
    吴恩达《机器学习》课程总结(7)正则化
    吴恩达《机器学习》课程总结(6)逻辑回归
    吴恩达《机器学习》课程总结(5)Octave教程
    吴恩达《机器学习》课程总结(4)多变量线性回归
  • 原文地址:https://www.cnblogs.com/Xing-Ling/p/12334816.html
Copyright © 2011-2022 走看看