支配樹

 

背景

  最近學到了一種新算法,可是很遺憾,在考試的時候,沒能看出來。html

  因而決定寫一篇博客來梳理一下。QAQnode

  【網上那麼多題解,因而我就換一種講法吧ios

   沒有太多的證實,更多的是本身的感性理解,若是有問題歡迎指出^-^算法

  


  

  支配樹的學習必定要按部就班QAQ數組

問題

  如今咱們有一張有向圖,求問當一個點 x 被刪去的時候,有多少個點沒法從起點到達?dom

  換句話說,從起點到點 x 必需要通過哪些點?ide

  顯然,咱們能夠經過刪邊+BFS,O(nm)的完成這個問題。學習

  有沒有什麼優秀的算法可以有效的下降複雜度呢?ui

  支配樹。spa

支配點的定義

  學習什麼是支配樹的構造以前,先了解一下,什麼是支配。

  在一張 有向圖 上面,若是點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的問題,很好的練手題

  小強和阿米巴

 

                                                                  謝謝!【完結撒花!

相關文章
相關標籤/搜索