支配树

背景

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

  于是决定写一篇博客来梳理一下。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的问题,很好的练手题

  小强和阿米巴

                                                                  谢谢!【完结撒花!

原文地址:https://www.cnblogs.com/qxyzili--24/p/11704702.html