zoukankan      html  css  js  c++  java
  • 支配树学习

    看某个ppt的时候看到了这个东西,就产生了一些兴趣。
    这个东西似乎有点鸡肋,我做了这么久的题,没有见过一道是用支配树解决的。
    不过这个算法真的很有趣。
    现在我还没有学完。打打博客加深理解。
    先推荐一篇很好的博客


    什么是支配树?

    现在我给你一个起点为rr的有向图,你需要求各种有关必经点的问题。
    先说一堆概念:
    支配点对于一个点xx,如果rrxx的所有路径上经过yy,那么yy支配xx
    最近支配点对于一个点xx,如果有一个点yy满足yy支配xx,并且所有支配xx的平凡支配点全都支配yy,那么yy就是xx的最近必经点,记作idom(x)idom(x)
    平凡支配点就是除了xx,之外的所有支配点。(其实网上的其它博客说rr也不包括在内,不过我觉得这样方便理解)

    定理1:对于任意的点,它的最近支配点都是唯一的。

    证明:假设这个点是xx,它有两个最近支配点分别为yyzz
    由定义得zz支配yy,并且yy支配zz
    因为yzy eq z,所以矛盾

    这个证明是不是特别简略……

    既然每个点的最近支配点都是唯一的,那我们自然而然地想到可以建立一棵树。
    对于点xx,将idom(x)idom(x)作为自己的父亲,方向为idom(x)idom(x)xx
    这就是支配树。

    性质?

    对于一个点xx,所有支配它的点都是它在支配树上的祖先。
    所以说我们可以利用它这个奇妙的性质做各种各样有关必经点的事情。

    知道了支配树的功能,现在我们的主要问题是,如何建立支配树。
    建立支配树就需要更多的概念和性质……


    更加详细的性质

    首先,从rr开始,建立一棵dfs树
    显然这个dfs树并不是支配树……
    记录一下它们的dfs序,在后面的叙述中,我直接用dfs序来表示它们的编号。

    一棵图中的边(x,y)(x,y)有四种情况:树枝边(这里指的是dfs树),前向边(xxyy的祖先),后向边(xxyy的后代),横插边(显然x>yx>y)。
    接下来我们用几个符号来表示各种关系(这个一定要记住):
    aba o b表示aa能直接通过一条边到达bb
    aba leadsto b表示aa能通过某条路径到达bb
    a˙ba dot o b表示aa能通过树枝边到达bb
    a+ba overset{+}{ o}b表示a˙ba dot o b并且aba eq b

    引理1(路径引理)
    对于两个点vvww,若vwvleq w,那么vvww的路径上必定经过他们的公共祖先(也就是LCA(v,w)LCA(v,w)及其所有祖先)

    证明:如果vvww的祖先,显然成立。
    因为横插边都是从大点去到小点,所以不可以通过横插边从vv所在的子树到达ww所在的子树。
    所以只能通过后向边跳上公共祖先,然后从公共祖先通过前向边跳下来。

    半支配点

    对于wrw eq r,都有一个半支配点sdom(w)sdom(w)
    sdom(w)=min{v(v0,v1, ,vk1,vk),v0=v,vk=w,1ik1,vi>w}sdom(w)=min{ v | exists (v_0,v_1,cdots,v_{k-1},v_k), v_0 = v, v_k = w, forall 1 leq i leq k-1, v_i>w }
    也就是说,从sdom(w)sdom(w)开始,存在一条从sdom(w)sdom(w)ww的路径,使得中间经过的所有点都比ww大。
    注意,半支配点并不一定是支配点,可能是支配点。


    引理2
    对于任意wrw eq r,满足idom(w)+widom(w) overset{+}{ o} w
    (证明显然:如果不是这样,它就可以直接通过树枝边来到达ww了,与定义矛盾)

    引理3
    对于任意wrw eq r,满足sdom(w)+wsdom(w) overset{+}{ o} w

    证明:首先,fawfa_wsdom(w)sdom(w)的一个候选,因为中间没有任何的点落脚。
    所以sdom(w)fawsdom(w)leq fa_w
    根据路径引理,如果sdom(w)sdom(w)在另一棵子树,那么必定经过他们的公共祖先,公共祖先小于xx,与定义矛盾

    引理4
    对于任意wrw eq r,满足idom(w)˙sdom(w)idom(w) dot o sdom(w)

    证明:如果不是这样,那么存在路径rsdom(w)wrleadsto sdom(w)leadsto w
    sdom(w)wsdom(w)leadsto w不经过idom(w)idom(w),与idomidom的定义矛盾

    引理5
    对于满足v˙wv dot o w的点vvwwv˙idom(w)v dot o idom(w)idom(w)˙idom(v)idom(w)dot o idom(v)
    这个似乎有点不好理解,其实可以根据上面的几个引理画出来:
    idom(v)+v˙idom(w)+widom(v) overset{+}{ o}vdot o idom(w)overset{+}{ o}w
    idom(w)˙idom(v)+v˙widom(w) dot o idom(v) overset{+}{ o}vdot o w
    也就是idom(v)+vidom(v)overset{+}{ o}v要么被idom(w)+widom(w)overset{+}{ o}w包含,要么不相交。(这样说不严谨,因为端点可能会重合)

    证明:如果不是这样,那么idom(v)+idom(w)+v+widom(v) overset{+}{ o} idom(w)overset{+}{ o} voverset{+}{ o} w
    存在r˙idom(v)v+wr dot o idom(v) leadsto v overset{+}{ o} w。(idom(w)idom(w)idom(v)idom(v)的真后代,不支配vv。所以可以绕过idom(w)idom(w)到达ww
    idomidom的定义矛盾。


    这后面的会难一些,不对,是难很多。
    揭示了idomidomsdomsdom的关系
    定理2
    对于任意wrw eq r,如果所有满足sdom(w)+u˙wsdom(w)overset{+}{ o}udot o wuu也满足sdom(w)sdom(u)sdom(w)leq sdom(u),则idom(w)=sdom(w)idom(w)=sdom(w)
    画一下就是:
    sdom(w)˙sdom(u)+u˙wsdom(w)dot o sdom(u) overset{+}{ o} u dot o w

    证明:由定理4得idom(w)˙sdom(w)idom(w) dot o sdom(w),所以如果我们想要证明idom(w)=sdom(w)idom(w)=sdom(w),就只需要证明sdom(w)sdom(w)支配ww即可。
    对于任意从rrww的路径,取最后一个小于sdom(w)sdom(w)的点xx
    假设yyxx的后继
    由于xx是最后一个小于sdom(w)sdom(w)的点,所以sdom(w)ysdom(w) leq y
    sdomsdom的定义得,必定存在yy满足sdom(w)˙y˙wsdom(w)dot o y dot o w(否则xx就是sdom(w)sdom(w)
    取最小的yy,假设yy不是sdom(w)sdom(w)
    由条件得sdom(w)sdom(y)sdom(w)leq sdom(y),由于x<sdom(w)x< sdom(w),所以xsdom(y)x eq sdom(y)
    所以存在x+t+yxoverset{+}{ o} toverset{+}{ o} y,由于xx是最后一个小于sdom(w)sdom(w)的点,所以sdom(w)˙t˙wsdom(w)dot o t dot o w
    在前面已经说过yy是最小的,所以矛盾
    因此yy就是sdom(w)sdom(w)
    所以任意路径经过sdom(w)sdom(w),因此sdom(w)sdom(w)支配ww
    idom(w)=sdom(w)idom(w)=sdom(w)

    这是前面说过的参考博客的证明,但是我感觉这种证明有点问题。
    于是自己用另一种方法证明了一遍:

    证明:我们同样是证明sdom(w)sdom(w)支配ww
    反证法,假设存在路径绕过sdom(w)sdom(w)到达ww
    这条路径必定可以看作,从rr绕过sdom(w)sdom(w)到达点xx(满足sdom(w)+x˙wsdom(w)overset{+}{ o}x dot o w),然后直接到ww
    1、当从某个比xx大的点来到xx时,由于sdom(w)sdom(x)sdom(w) leq sdom(x),所以sdom(w)˙sdom(x)sdom(w) dot o sdom(x),不可能直接通过树枝边从rr到达sdom(x)sdom(x),不存在这样的路径。
    2、当从某个比xx小的点来到ww时,
    (1)如果从树枝边走来:
    如果xxsdom(w)sdom(w)的儿子,那么sdom(x)=sdom(w)sdom(x)=sdom(w),所以不能到达。
    用归纳证明的思想,可得后面的点都不能到达。所以这个点不能在sdom(w)sdom(w)ww之间的路径上。
    (2)如果从sdom(w)sdom(w)的子树外走来
    这个点显然可以作为sdom(x)sdom(x)的取值,但是sdom(w)sdom(x)sdom(w)leq sdom(x),矛盾
    综上,这种路径不存在。

    可能还是有点不严谨,不过理解一下就好。

    定理3
    对于任意wrw eq r,设uu为满足sdom(w)+u˙wsdom(w) overset{+}{ o} u dot o wsdom(u)sdom(u)最小的uu,如果sdom(u)sdom(w)leq sdom(w),那么idom(w)=idom(u)idom(w)=idom(u)
    画图:sdom(u)˙sdom(w)+u˙wsdom(u) dot o sdom(w) overset{+}{ o} u dot o w

    证明:由引理5得idom(w)˙idom(u)idom(w) dot o idom(u)u˙idom(w)udot o idom(w),由引理44得后面的这种情况不存在。
    画张图理解一下:idom(w)˙idom(u)˙sdom(w)+u˙widom(w)dot o idom(u) dot o sdom(w)overset{+}{ o}u dot o w
    所以,我们要证明idom(w)=idom(u)idom(w)=idom(u),只需要证明idom(u)idom(u)支配ww即可。
    类似于之前的思路:
    假设有某一条路径绕过idom(u)idom(u)到达ww
    1、从大于ww点到达ww
    如果这样,存在一个点xx,满足sdom(x)sdom(x)的定义(不一定最小)。
    如果x+ux overset{+}{ o} u
    因为idom(u)idom(u)支配uu,而又存在rx+ur leadsto x overset{+}{ o} u,所以idom(u)idom(u)支配xx
    所以不存在某种绕过sdom(w)sdom(w)到达xx的方案。
    如果u˙xudot o x,那么继续考虑能不能到达xx(像个递归的过程),最终总会到x+uxoverset{+}{ o} u的状况,因此也是不存在的。
    2、从小于ww点到达ww
    (1)从树枝边走来
    类似于之前的情况,如果存在这样的路径,必定能从起点绕过idom(u)idom(u)到达uuww之间的某个点。(前面已经证明过绕idom(u)idom(u)到达idom(u)idom(u)uu之间的点不存在)
    但是连sdom(u)sdom(u)最小的uu都不成立,那其他的点也不可能了。
    (2)从idom(u)idom(u)外走来
    如果可以这么走,那它就应该是sdom(w)sdom(w),矛盾。
    综上所述,不存在这样的路径


    推论1
    对于任意rwr eq w,如果有满足sdom(w)+u˙wsdom(w) overset{+}{ o} u dot o wsdom(u)sdom(u)最小的uu,那么
    idom(w)={sdom(w)(sdom(u)=sdom(w))idom(u)(sdom(u)<sdom(w))idom(w) = left { egin{aligned}& sdom(w)&(sdom(u)=sdom(w))&\ &idom(u)&(sdom(u)<sdom(w))&end{aligned} ight .
    这个东西是通过上面的定理2和定理3推过来的。但是在这里,我们发现sdom(u)sdom(w)sdom(u)leq sdom(w)的,为什么呢?
    我感觉博客中简略的证法好像有问题,所以我自己想了一下:

    证明:如果存在某种情况使得sdom(u)>sdom(w)sdom(u)>sdom(w)
    画图就是这样:sdom(w)+sdom(u)+u˙wsdom(w) overset{+}{ o} sdom(u) overset{+}{ o} u dot o w
    显然sdom(sdom(u))<sdom(u)sdom(sdom(u))<sdom(u),所以此时的sdom(u)sdom(u)应该是uu
    我们已经要求过sdom(u)sdom(u)最小,所以矛盾。
    因此sdom(u)sdom(u)sdom(u)leq sdom(u)


    如果暴力求sdomsdom则太慢,于是就有了下面这个强大的定理:
    定理4
    对于任意wrw eq rsdom(w)=min({v(v,w)E,v<w}{sdom(u)u>w,(v,w)E,u˙v})sdom(w) = min({v | (v, w) in E , v < w } cup {sdom(u) | u > w , exists (v, w) in E , u dot o v} )
    其实这个东西特别像是一个递推式。
    别人博客上有证明,我理解式子后,第一反应是,这个东西需要证明?
    如果仅仅把它当作一个递推式来看,那么它还是特别好理解的。
    证明一下吧……

    证明:设等号右边的式子的结果为xx,显然sdom(w)xsdom(w)leq x。现在要证明xsdom(w)xleq sdom(w)
    如果sdom(w)sdom(w)ww中只经过一条边,显然(sdom(w),w)E(sdom(w),w)in Esdom(w)<wsdom(w)<w,所以xsdom(w)xleq sdom(w)
    如果不只经过一条边,设这条路径上的最后一个点为lastlast。在sdom(w)sdom(w)lastlast之间找到一个最小的点pp,显然sdom(w)sdom(w)pp上经过的点都比pp大,所以sdom(p)sdom(w)sdom(p)leq sdom(w)
    同时sdom(p)sdom(p)满足等式右边的条件,满足p˙lastpdot o last(last,w)E(last,w)in E
    所以sdom(p)sdom(p)xx的一个候选,所以xsdom(p)sdom(w)xleq sdom(p)leq sdom(w),所以xsdom(w)x leq sdom(w)
    综上,xsdom(w)xleq sdom(w)
    所以sdom(w)=xsdom(w)=x


    Lengauer-Tarjan算法

    前面推的东西都是为这个算法铺垫的。这个算法用到定理4和推论1。
    先说说步骤:
    1、dfs一遍,求出dfndfn
    2、按照dfndfn倒着求出sdomsdom
    3、确定idom=sdomidom=sdomidomidom,其它的暂时不理它。
    4、按照dfndfn顺着找没有计算的idomidom,计算。

    第1步不说。
    第2步和第3步可以放在一起来写。
    我们要用一个数据结构来维护一个森林,每个点到根的路径上最小的sdomsdom。这个数据结构可以用并查集。我们记作xx的这个东西为eval(x)eval(x)
    看看定理4的式子:sdom(w)=min({v(v,w)E,v<w}{sdom(u)u>w,(v,w)E,u˙v})sdom(w) = min({v | (v, w) in E , v < w } cup {sdom(u) | u > w , exists (v, w) in E , u dot o v} )
    由于我们是倒过来做的,所以对于点ww来说,若它的前驱v>wv>w,那么上式右边的uueval(v)eval(v)是最合适的。如果前驱v<wv<w,它们没有处理过,可以把sdomsdom的初值设为它们自己、
    通过定理4可以求出所有点的sdomsdom
    接下来将它挂在它的sdomsdom上。每做完一个子树之后,在并查集上将子树和父亲合并,然后处理留在父亲上的sdomsdom,也就是通过引理1来计算idomidom。具体来说,如果父亲是xx,挂在父亲上的点ww,它们满足sdom(w)=xsdom(w)=x,如果sdom(eval(w))=xsdom(eval(w))=x,那么idom(w)=sdom(w)=xidom(w)=sdom(w)=x,否则idom(w)=idom(eval(w))idom(w)=idom(eval(w)),这个东西先不要求出来,可以在实现的时候记作idom(w)=eval(w)idom(w)=eval(w)
    最后就是第4步,按照dfndfn从小到大枚举,如果idom(w)=sdom(w)idom(w)=sdom(w),说明idom(w)idom(w)已经被处理过,不理它;否则,idom(x)=idom(idom(x))idom(x)=idom(idom(x))
    这样就求出所有点的idom(x)idom(x)了。

    清点一下要维护的东西:
    idomidomsdomsdom数组
    并查集维护evaleval
    每个点的前驱predpred
    将点挂在sdomsdom用的数组(链表)bucbuc
    以及一些零零散散的东西。

    这个算法的时间复杂度实际上是O(nlgn)O(n lg n)的,我受到了那篇博客的影响,知道并查集能被卡成lglg级别。当然这种情况大多数是不会见到的,毕竟没几个人来卡你的并查集,就算卡,凭借着并查集的优秀常数也不会多慢。
    姑且认为是O(nα(n))O(n alpha(n))的,几乎都是这样了。


    代码

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 200000
    #define M 300000
    int n,m;
    struct EDGE{
        int to;
        EDGE *las;
    } e[M+1],pe[M+1],te[M+1],be[M+1];
    //这四个东西都用链表来存。e表示图边,pe表示反向边(pred),te表示建出来的支配树,be表示buc
    int ne,pne,tne,bne;
    EDGE *last[N+1],*plast[N+1],*tlast[N+1],*blast[N+1];
    int dfn[N+1],nowdfn,ver[N+1]; //dfn为dfs的时间戳,ver为dfs序。
    int idom[N+1],sdom[N+1];
    int fa[N+1];
    void init(int x){//初始化,求出dfn,ver
        dfn[x]=++nowdfn,ver[nowdfn]=x;
        sdom[x]=x;
        for (EDGE *ei=last[x];ei;ei=ei->las)
            if (!dfn[ei->to]){
                fa[ei->to]=x;
                init(ei->to);
        	}
    }
    int top[N+1],eval[N+1];//这些就是并查集维护的东西
    inline int sdom_min(int a,int b){return dfn[sdom[a]]<dfn[sdom[b]]?a:b;}
    inline int dfn_min(int a,int b){return dfn[a]<dfn[b]?a:b;}
    void gettop(int x){
        if (top[x]==x)
            return;
        gettop(top[x]);
        eval[x]=sdom_min(eval[x],eval[top[x]]);
        top[x]=top[top[x]];
    }
    int siz[N+1];//最后的答案(洛谷的模板题输出支配树中每个点的子树大小)
    void get_siz(int x){
        siz[x]=1;
        for (EDGE *ei=tlast[x];ei;ei=ei->las)
            get_siz(ei->to),siz[x]+=siz[ei->to];
    }
    int main(){
        scanf("%d%d",&n,&m);
        for (int i=1;i<=m;++i){
            int u,v;
            scanf("%d%d",&u,&v);
            e[++ne]={v,last[u]};
            last[u]=e+ne;
            pe[++pne]={u,plast[v]};
            plast[v]=pe+pne;
        }
        init(1);
         for (int i=1;i<=n;++i)
            top[i]=eval[i]=i;
        for (int i=n;i>=1;--i){
            int x=ver[i],y=fa[x];
            for (EDGE *ei=plast[x];ei;ei=ei->las)
                gettop(ei->to),sdom[x]=dfn_min(sdom[x],sdom[eval[ei->to]]);//枚举前缀计算sdom
            be[++bne]={x,blast[sdom[x]]};//将x挂到sdom[x]上
            blast[sdom[x]]=be+bne;
            if (y)
                for (top[x]=y;blast[y];blast[y]=blast[y]->las){//将自己的子树和父亲合并;清空挂在父亲上的点,用来求idom   	
                    int v=blast[y]->to;
                    gettop(v);
                    if (sdom[eval[v]]==sdom[v])
                        idom[v]=sdom[v];
                    else
                        idom[v]=eval[v];
                }
        }
        for (int i=1,x=ver[i];i<=n;++i,x=ver[i])//将没有计算完idom的点计算好
            if (idom[x]!=sdom[x])
                idom[x]=idom[idom[x]];
        for (int i=2;i<=n;++i){
        	te[++tne]={i,tlast[idom[i]]};
        	tlast[idom[i]]=te+tne;
        }
        get_siz(1);
        for (int i=1;i<=n;++i)
            printf("%d ",siz[i]);
        return 0;
    }
    
  • 相关阅读:
    idea解决Maven jar依赖冲突(四)
    代码规范:idea上添加阿里巴巴Java开发插件
    一起MySQL时间戳精度引发的血案
    JVM Code Cache空间不足,导致服务性能变慢
    通过SOFA看Java服务端如何实现运行时的模块化
    谈谈我对SOFA模块化的理解
    谈谈我对SOFA模块化的理解
    一文谈尽边缘计算
    JVM调优实战:G1中的to-space exhausted问题
    JVM调优实战:G1中的to-space exhausted问题
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145243.html
Copyright © 2011-2022 走看看