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

    背景

      最近学到了一种新算法,但是很遗憾,在考试的时候,没能看出来。

      于是决定写一篇博客来梳理一下。QAQ

      【网上那么多题解,于是我就换一种讲法吧

       没有太多的证明,更多的是自己的感性理解,如果有问题欢迎指出^-^

      


      

      支配树的学习一定要循序渐进QAQ

    问题

      现在我们有一张有向图,求问当一个点 x 被删去的时候,有多少个点无法从起点到达?

      换句话说,从起点到点 x 必须要经过哪些点?

      显然,我们可以通过删边+BFS,O(nm)的完成这个问题。

      有没有什么优秀的算法能够有效的降低复杂度呢?

      支配树。

    支配点的定义

      学习什么是支配树的构造之前,先了解一下,什么是支配。

      在一张 有向图 上面,如果点A被删除,那么B点就无法到达,那么称 A点是B点的支配点 

      很显然,对于每个点来说,有且至少有一个支配点,并且这些支配点之间呈现传递关系。【即 A支配B,B支配C,就有A支配C

    支配点的选择范围

      首先,我们需要一张有向图。

      

      观察上图,就拿点7来举例子。

      很明显,能够支配他的点就是1,4.

      问题就出现了,1.4这两个点对于7点来说有什么特殊性质吗?

        1. 放在一张有向图中来看,显然,他们是父子关系。

        2. 对于一个DAG,1.4的拓扑序小于7点。

      所以显然,我们可以通过一通乱搞,把每一个点的支配点集求出。【先别管怎么搞,假定已经会找了QAQ

      那么我们就可以通过每个点向他的直系支配点【最近支配点 连边的方式构造出一颗

      就比如上面这张图构造成一颗树之后就是下图

      

      这棵树就是支配树,从起点走向每一点所路过的所有点就是他的支配点。

    支配树的构造

      树形结构

        对于一棵树来说,他本身就是自己的支配树。

      DAG

        DAG上的问题向来就是简单的多了,但是看出来是一个DAG可不太简单。【这我没办法,看不出来就打一般代码就完事

        同样我们需要一个DAG来感受一下。

        

        肉眼可见,点5的支配点集为{1}

        试分析:

          由于点5有两个入点2,4,所以2,4都不是支配点。

          那么问题就转化成了,从起点1到达2和4的路径上面有哪个点是必经的?

          先只考虑最近的支配点【其余的点可以从通过求支配点的支配点来得到

          很容易就能想到,这个最近支配点就是点2,4的LCA

          那么接下来就进行不断地传递求出所有的支配点。

        那么我们就可以得到一种求法

          倍增求出能够到达这个点 x 的所有点的LCA,记为father,那么father就是x在支配树上面的父亲。

        复杂度O(nlogn)

      一般有向图

        还记得支配点的定义吗?

        删除x的支配点之后,就无法从起点走到x点。

        先假想,这不是一个有向图,他的边是无向的,那么这个问题想必就简单多了。

        很明显,这就是一个割点问题。

        那么在有向图的情况下,tarjan能不能发挥一定的作用呢?

        接下来我们就用dfn来尝试做一些操作。

        首先,我们需要把一张图给扣出来一棵树【起点为根

        如何选取树上的点呢?

        我们学过一个东西,dfs树。

        dfs树有一个很好的性质:

          若图中两点x,y的 dfn[x] <= dfn[y],则任意从x到y的路径必然包含他们在dfs树中的一个公共祖先

        那么我们就可以选取这个dfs树作为我们选取的生成树。

        接下来,进行一些定义【一定要记牢,不然之后会乱。

          1. 半支配点

            存在一个从点u到点v的路径中(不包括u,v),所有dfs树的点的dfn都大于dfn[v],并且如果u是v在dfs树上的父节点,那么u也是v的半支配点。【说白了就是支配点的选择范围。

            不过如果要记录一个点所有的支配点会很困难。不过,既然支配点之间具有传递关系,那么我们就可以只记录最近的半支配点。

            所以我们定义一个数组semi[ ]【Semi-domination 半支配 来记录最近的半支配点

          2.祖先

            刚刚说了需要记录最近半支配点,然后通过传递的方式来还原出来整条传递关系。那么我们就需要记录一个数组来表示祖先。

            所以我们定义一个数组anc[ ]【ancestry 祖先 来记录传递关系

            之前提到我们要通过dfn[ ]来做,并且提到了dfn值之间的大小关系,放到支配树上面,就是x的直系父亲【最近anc 需要满足:

              semi[直系父亲] = semi[x]

            这是建树很重要的一步,然而一次次的查找太浪费时间了,所以我们还需要定义一个数组。

            定义数组min_anc[ ]表义为每个点的dfs树上面的semi[ ]值最小的祖先

        这样的话,我们就可以在找出的这个dfs树上面通过semi[ ]的传递关系,建出来这个新的支配树了。

    一般图建立支配树的实现总结

      上面并没有讲的很详细,很多证明都是感性理解的过去了。【不过也不太重要,理解意思就可以了。如果想要细致的理解的话,可以看这篇博客

      可能看到这里会有一些逻辑混乱【跟我的语文水平也有关系QAQ,那么我就再简单的总结一下。

      首先,我们从一张图中扣出一棵具有良好性质的树。【dfs序的处理

      然后,计算每个点的半支配点,这里再更细的说一下具体的实现吧:

        1. 在点x的祖先链上面处理出来semi[ ]的最小值。

        2. 通过带权并查集给根找到父亲。【通过semi[ ]的传递关系来构造,上面min_anc[ ]的定义时有写。

      int fi_fa(int x) {
          if(x==anc[x]) 
              return x;
          int father=anc[x];
          anc[x]=fi_fa(anc[x]);
          if(dfn[semi[min_anc[x]]]>dfn[semi[min_anc[father]]])
              min_anc[x]=min_anc[father];
          return anc[x];
      }

      再者,通过半支配点来计算支配点。

      最后,向直系支配点连边构造支配树。

    时间复杂度

      网上说这个算法的复杂度是 O(n α(n))


     

    小结

      支配树相关的内容到此就完结了,可能有很多的没说清楚的地方,欢迎能看到这里的你在评论区指出我的不足之处【如果没有看懂的话一定要提出自己的问题,我们一起完善这篇博客

      最后,有什么不懂的写法,就看看我的代码吧【这是结合了我的理解和网上很多代码的产物,可能不是最短的,但是很清晰QAQ

      代码的题面来自luogu的模板题

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #include<queue>
    using namespace std;
    
    const int maxn=3e5+7;
    int n,m;
    struct node{
        int head[maxn],ecnt;
        struct ss{
            int to,nxt;
        }tr[maxn<<1];
        void add(int a,int b) {
            tr[++ecnt].nxt=head[a];
            tr[ecnt].to=b;
            head[a]=ecnt;
            return;
        }
    }mp,un_mp,dfs_tr,undfs_tr,nw_tr;//原图,反图,dfs树,反dfs树,支配树 
    
    int cnt,dfn[maxn],id[maxn],fa[maxn];
    
    void dfs(int x) {//寻找dfs树 
        id[dfn[x]=++cnt]=x;
        for(int i=mp.head[x];i;i=mp.tr[i].nxt) {
            int y=mp.tr[i].to;
            if(!dfn[y]) {
                dfs(y);
                fa[y]=x;
                dfs_tr.add(x,y);
            }
        }
        return;
    }
    
    int anc[maxn],min_anc[maxn],semi[maxn];//Semi-domination半支配 表示v点的所有半支配点的最小的那个
    /**半支配点: 
    存在从一个点u到v的路径中(不包括u,v),所有dfs树的点的dfn都大于v的dfn
    如果u是v在dfs树上的父节点,那么u也是v的半支配点
    **/ 的semi最小的那个祖先,所以semi[mn[x]]=semi[x];
    int fi_fa(int x) {
        if(x==anc[x]) 
            return x;
        int father=anc[x];
        anc[x]=fi_fa(anc[x]);
        if(dfn[semi[min_anc[x]]]>dfn[semi[min_anc[father]]])
            min_anc[x]=min_anc[father];
        return anc[x];
    }
    
    void sp_tarjan() {
        for(int i=1;i<=n;i++) 
            anc[i]=min_anc[i]=semi[i]=i;
        for(int j=n;j>1;j--) {
            int x=id[j];
            if(!x)
                continue;
            int res=j;
            for(int i=un_mp.head[x];i;i=un_mp.tr[i].nxt) {
                int y=un_mp.tr[i].to;
                if(!dfn[y])
                    continue;
                if(dfn[y]<dfn[x])
                    res=min(res,dfn[y]);
                else {
                    fi_fa(y);
                    res=min(res,dfn[semi[min_anc[y]]]);
                }
            }
            semi[x]=id[res];
            anc[x]=fa[x];
            dfs_tr.add(semi[x],x);
        }
        return;
    }
    
    int in[maxn],dept[maxn],fath[maxn][30];
    
    int lca(int x,int y) {
        if(dept[x]<dept[y]) 
            swap(x,y);
        int d=dept[x]-dept[y];
        for(int i=0;i<=20;i++) 
            if((1<<i)&d)
                x=fath[x][i];
        if(x==y)
            return x;
        for(int i=20;i>=0;i--) {
            if(fath[x][i]!=fath[y][i]) {
                x=fath[x][i];
                y=fath[y][i];
            }
        }
        return fath[x][0];
    }
    
    void built_tree(int x) {
        int son=undfs_tr.tr[undfs_tr.head[x]].to;
        for(int i=undfs_tr.head[x];i;i=undfs_tr.tr[i].nxt) {
            int y=undfs_tr.tr[i].to;
            son=lca(son,y);
        }
        dept[x]=dept[son]+1;
        fath[x][0]=son;
        nw_tr.add(son,x);
        for(int i=1;i<=20;i++)
            fath[x][i]=fath[fath[x][i-1]][i-1];
        return;
    }
    
    void topu() {
        for(int x=1;x<=n;x++) {
            for(int i=dfs_tr.head[x];i;i=dfs_tr.tr[i].nxt) {
                int y=dfs_tr.tr[i].to;
                in[y]++;
                undfs_tr.add(y,x);
            }
        }
        for(int i=1;i<=n;i++) {
            if(!in[i]) {
                undfs_tr.add(i,0);
                dfs_tr.add(0,i);
            }
        }
        queue<int> q;
        q.push(0);
        while(!q.empty()) {
            int x=q.front();
            q.pop();
            for(int i=dfs_tr.head[x];i;i=dfs_tr.tr[i].nxt) {
                int y=dfs_tr.tr[i].to;
                if(--in[y]<=0) {
                    q.push(y);
                    built_tree(y);
                }
            }
        }
        return;
    }
    
    int siz[maxn];
    
    void re_dfs(int x) {
        siz[x]=1;
        for(int i=nw_tr.head[x];i;i=nw_tr.tr[i].nxt) {
            int y=nw_tr.tr[i].to;
            re_dfs(y);
            siz[x]+=siz[y];
        }
        return;
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++) {
            int x,y;
            scanf("%d%d",&x,&y);
            mp.add(x,y);
            un_mp.add(y,x);
        }
        dfs(1);
        sp_tarjan();
        topu();
        re_dfs(0);
        for(int i=1;i<=n;i++) 
            cout<<siz[i]<<" ";
        cout<<endl;
        return 0;
    }
    支配树模板

    题目推荐

      luogu模板题自然就不用说了

      luogu灾难 【一个DAG的问题,很好的练手题

      小强和阿米巴

                                                                      谢谢!【完结撒花!

  • 相关阅读:
    python读取数据写入excel
    English Study!
    ODOO里视图开发案例---定义一个像tree、form一样的视图
    更改gradle中央仓库,加快访问速度
    hadoop解决集群启动时某个slave的datanode挂掉问题
    ssh免密登录
    大数据集群脚本xcall和xsync
    虚拟机启动后黑屏并无法关闭
    快照与克隆的区别(来自转载)
    VMware12 打不开Centos6.8系统
  • 原文地址:https://www.cnblogs.com/qxyzili--24/p/11704702.html
Copyright © 2011-2022 走看看