最近學到了一種新算法,可是很遺憾,在考試的時候,沒能看出來。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來感覺一下。
肉眼可見,點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樹做爲咱們選取的生成樹。
接下來,進行一些定義【必定要記牢,否則以後會亂。
存在一個從點u到點v的路徑中(不包括u,v),全部dfs樹的點的dfn都大於dfn[v],而且若是u是v在dfs樹上的父節點,那麼u也是v的半支配點。【說白了就是支配點的選擇範圍。
不過若是要記錄一個點全部的支配點會很困難。不過,既然支配點之間具備傳遞關係,那麼咱們就能夠只記錄最近的半支配點。
因此咱們定義一個數組semi[ ]【Semi-domination 半支配 來記錄最近的半支配點。
剛剛說了須要記錄最近半支配點,而後經過傳遞的方式來還原出來整條傳遞關係。那麼咱們就須要記錄一個數組來表示祖先。
因此咱們定義一個數組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的問題,很好的練手題
小強和阿米巴