【学习小记】支配树【图论】
Preface
給定一個有向圖和一個起點 s t st st,我們需要知道起點到某個點的關于必經點的信息。
若起點到點v的所有路徑均經過點u,則我們稱點u支配點v,顯然一個點支配自己本身
顧名思義,支配樹就是由某些支配關系構成的樹。
定義
約定一些記號
( u , v ) (u,v) (u,v),表示一條從u到v的有向邊
f a ( u ) fa(u) fa(u),表示 u u u在DFS樹上的父親。
d f n ( i ) dfn(i) dfn(i)為從 s t st st開始DFS整個圖, i i i號點的 d f s dfs dfs時間戳。
P a t h ( u , v ) Path(u,v) Path(u,v),表示DFS樹上 u u u到 v v v的路徑上的點集
P a t h ( u , v ) / u Path(u,v)/u Path(u,v)/u表示路徑上點集中除去點 u u u剩余的集合
我們定義一個點 u u u的最近支配點 i d o m ( u ) idom(u) idom(u),它支配 u u u,且它也被除u以外的所有支配u的點支配
性質
顯然 i d o m ( u ) idom(u) idom(u)唯一存在,且若我們重新建一個圖,所有的 i d o m ( u ) idom(u) idom(u)與 u u u連一條邊,那么這個新圖是一棵以 s t st st為根的樹,也就是說 i d o m idom idom不會成環(可以用反證法簡單證明)。
這棵樹就是我們說的支配樹。
i d o m ( u ) idom(u) idom(u)一定是 u u u在 d f s dfs dfs樹上的祖先。(一樣可以反證法簡單證明)
問題就是要求出 i d o m idom idom。
求解
為了求出 i d o m idom idom,我們引入半必經點的概念。
半必經點
回歸tarjan求強連通分量的算法,我們來觀察DFS過程中會出現哪些邊。
- 樹邊,DFS樹上的父親——兒子邊。
- 后向邊,祖先向后代連的非樹邊的邊。
- 返祖邊,DFS序大的后代向DFS序小的祖先連的邊
- 橫叉邊,DFS序大的點向DFS序小的點連的邊,且沒有祖先后代關系。
容易證明不存在上面四種邊以外的邊。
記 s e m i ( u ) semi(u) semi(u)表示從 u u u開始,沿著邊反著走,能夠中途不經過u的祖先,最后到達的深度最淺( d f n dfn dfn最小)的 u u u的祖先。
形式化的, s e m i ( u ) semi(u) semi(u)是 u u u的祖先,且存在原圖中的一條路徑 p 1 . . . p k p_1...p_k p1?...pk?, p 1 = s e m i ( u ) , p k = u , ? i = 2 k ? 1 d f n ( p i ) > d f n ( u ) p_1=semi(u),p_k=u,\forall_{i=2}^{k-1}dfn(p_i)>dfn(u) p1?=semi(u),pk?=u,?i=2k?1?dfn(pi?)>dfn(u)
可以發現這兩種定義是等價的,因為不可能通過反向邊走到DFS序更小的且不是祖先的點去。
我們考慮它可能怎么走,要么通過樹邊/后向邊的反向邊直接走到一個祖先,要么通過返祖邊/橫叉邊的反向邊走到某些 d f n dfn dfn更大的點 p r e u pre_u preu?,然后在 P a t h ( s t , p r e u ) Path(st,pre_u) Path(st,preu?)上找一個 w w w,滿足 d f n ( w ) ≥ d f n ( u ) dfn(w)\geq dfn(u) dfn(w)≥dfn(u),所有的 s e m i ( w ) semi(w) semi(w)的最淺的那個就用來更新 s e m i ( u ) semi(u) semi(u)。
可以發現最淺的 s e m i ( w ) semi(w) semi(w)一定是 u u u的祖先,因此我們不必擔心不合法的問題。
那么我們就得到了 s e m i semi semi的求法,按照DFS序倒序枚舉所有點 u u u,按照上面的做法來不斷更新,查詢祖先可以用帶權并查集來做,求出一個點的 s e m i semi semi之后將它的所有樹邊兒子與它合并即可。
問題在于求 i d o m idom idom
我們考慮 v ∈ P a t h ( s e m i ( u ) , u ) / s e m i ( u ) v\in Path(semi(u),u)/semi(u) v∈Path(semi(u),u)/semi(u),找到 s e m i ( v ) semi(v) semi(v)最淺的一個。
有定理:若 s e m i ( v ) = s e m i ( u ) semi(v)=semi(u) semi(v)=semi(u),則 i d o m ( u ) = s e m i ( u ) idom(u)=semi(u) idom(u)=semi(u),否則 i d o m ( u ) = i d o m ( v ) idom(u)=idom(v) idom(u)=idom(v)
這個定理請自行理解…我也不會證
有了這個定理之后我們可以將 u u u掛在 s e m i ( u ) semi(u) semi(u)上,掃到 s e m i ( u ) semi(u) semi(u)的時候就找到 u u u相應的 v v v,若 s e m i ( u ) = s e m i ( v ) semi(u)=semi(v) semi(u)=semi(v)就直接 i d o m ( u ) = s e m i ( u ) idom(u)=semi(u) idom(u)=semi(u),否則因為此時 i d o m ( v ) idom(v) idom(v)還沒有求出來,我們可以先將 i d o m ( u ) idom(u) idom(u)指向 v v v,最后全部都做完以后按照DFS序正序掃一遍將這部分的 i d o m ( u ) idom(u) idom(u)改成 i d o m ( i d o m ( u ) ) idom(idom(u)) idom(idom(u))即可。
時間復雜度 O ( n log ? n ) O(n\log n) O(nlogn)(因為不能按秩合并)
Code
洛谷P5180,支配樹模板題。
#include <bits/stdc++.h> #define fo(i,a,b) for(int i=a;i<=b;++i) #define fod(i,a,b) for(int i=a;i>=b;--i) #define N 200005 #define M 300005 using namespace std; vector<int> pre[N]; int fs[N],nt[M],dt[M],pr[M],n,m,m1;int semi[N],idom[N],ft[N],fp[N],dfn[N],dfw[N],fa[N]; vector<int> ids[N]; int gmin(int x,int y) {return (!y||(dfn[x]<dfn[y]&&x))?x:y; } void link(int x,int y) {nt[++m1]=fs[x];dt[fs[x]=m1]=y; } void dfs(int k) {dfw[dfn[k]=++dfn[0]]=k;for(int i=fs[k];i;i=nt[i]){int p=dt[i];if(!dfn[p]) fa[p]=k,dfs(p);if(dfn[k]<dfn[p]) semi[p]=gmin(semi[p],k);else pre[p].push_back(k);} } int getf(int k) {if(!ft[k]||ft[k]==k) return k;int p=getf(ft[k]);fp[k]=(dfn[semi[fp[k]]]<dfn[semi[fp[ft[k]]]])?fp[k]:fp[ft[k]];return ft[k]=p; } int fd(int k) {getf(k);return fp[k]; } void merge(int x,int y) {int fx=getf(x),fy=getf(y);ft[fy]=fx,fp[fy]=(dfn[semi[fp[fy]]]<dfn[semi[fp[fx]]])?fp[fy]:fp[fx]; } void make() {fod(i,n,1){int k=dfw[i];if(i>1){for(int u:pre[k]){ int v=fd(u);semi[k]=gmin(semi[k],semi[v]);}ids[semi[k]].push_back(k); }for(int u:ids[k]){int v=fd(u);idom[u]=(semi[v]==k)?k:v; }fp[k]=k;for(int i=fs[k];i;i=nt[i]) if(fa[dt[i]]==k) merge(k,dt[i]);}for(int i=2,k=dfw[2];i<=n;++i,k=dfw[i]) if(idom[k]!=semi[k]) idom[k]=idom[idom[k]]; } int sz[N]; int main() {cin>>n>>m;fo(i,1,m){int x,y;scanf("%d%d",&x,&y);link(x,y),pre[y].push_back(x);}dfs(1);make();fod(i,n,1){sz[dfw[i]]++;sz[idom[dfw[i]]]+=sz[dfw[i]]; }fo(i,1,n) printf("%d ",sz[i]); }總結
以上是生活随笔為你收集整理的【学习小记】支配树【图论】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: xp计算机无法关机,WinXP电脑无法关
- 下一篇: 把代码做成笔记——Jupyter Not