【学习笔记】支配树
文章目錄
- 0.概述
- 1.有關 dfs\rm dfsdfs 樹
- 1.1.定義
- 1.2.性質
- 1.2.1.非樹邊
- 1.2.2.dfn\text{dfn}dfn 推論
- 2.有關支配樹
- 2.1.半支配點與 semisemisemi
- 2.1.1.性質
- 2.1.2.求 semisemisemi
- 2.2.支配點
- 2.3.支配樹
- 2.3.1.定義
- 2.3.2.存在性
- 2.3.3.建支配樹
- 2.4.代碼實現
- 3.例題
- 3.1.板題
- 4.后記
0.概述
用來求一張圖中,從某個定點 sss 出發,到達某一點 eee 的路徑上,必須經過哪些點。
這玩意兒有什么用呢?沒什么用。 我只是個普及組選手啊😓
1.有關 dfs\rm dfsdfs 樹
1.1.定義
在一個圖中,以某一點為起點,進行深度優先遍歷,將所有走過的邊當作樹邊,形成的 樹 。
注意:邊可能是有向的。所以這并非一個傳統意義上的樹。
1.2.性質
記 dfn?(u)\operatorname{dfn}(u)dfn(u) 為 uuu 是第幾個被訪問的(回溯不計入訪問次數)。
1.2.1.非樹邊
在有向圖中,對于任意非樹邊 ?u,v?\langle u,v\rangle?u,v?,要么 uuu 是 vvv 的祖先,要么 dfn(u)>dfn(v)\text{dfn}(u)>\text{dfn}(v)dfn(u)>dfn(v) 。
證明是輕松的。在 uuu 回溯時,對于所有已經訪問的節點 xxx,要么 dfn(x)<dfn(u)\text{dfn}(x)<\text{dfn}(u)dfn(x)<dfn(u),要么 xxx 為 uuu 的子樹內一點。而 vvv 都不滿足,則此時必將 dfs\rm dfsdfs 到 vvv,進而 (u,v)(u,v)(u,v) 為樹邊,出現矛盾。
1.2.2.dfn\text{dfn}dfn 推論
對于任意兩個點 u,v(u≠v)u,v\;(u\ne v)u,v(u?=v),不妨設 dfn(u)<dfn(v)\text{dfn}(u)<\text{dfn}(v)dfn(u)<dfn(v),記 lca(u,v)=xlca(u,v)=xlca(u,v)=x 。
- 對于 vvv 到 xxx 的樹上路徑中一點 yyy,一定有 dfn(u)<dfn(y)\text{dfn}(u)<\text{dfn}(y)dfn(u)<dfn(y) 。
- 對于 uuu 到 xxx 的樹上路徑中一點 yyy,一定有 dfn(y)<dfn(v)\text{dfn}(y)<\text{dfn}(v)dfn(y)<dfn(v) 。
其正確性是顯然的;只需要畫一張圖就可以領會其中真諦了。用大白話來說就是,兩個點的 dfn\rm dfndfn 比較,在不超過 lcalcalca 的位置都是一樣的。
2.有關支配樹
如果圖是無向圖,無向邊轉化為兩條有向邊。故以下內容均討論有向圖。
為了表述方便,u?vu\Rightarrow vu?v 表示 uuu 通過一條原圖中存在的有向邊 ?u,v?\langle u,v\rangle?u,v? 到達 vvv,即二者直接相連。而 u→vu\rightarrow vu→v 表示 u???vu\Rightarrow \cdots \Rightarrow vu???v 或 u?vu\Rightarrow vu?v 。
2.1.半支配點與 semisemisemi
對于一個點 uuu,若存在一條路徑 v→u(v≠u)v\rightarrow u\;(v\ne u)v→u(v?=u),使得路徑上除 uuu 和 vvv 的點 xxx 都滿足 dfn(x)>dfn(u)\text{dfn}(x)>\text{dfn}(u)dfn(x)>dfn(u),則 vvv 為 uuu 的半支配點。
規定 semi(x)semi(x)semi(x) 為 xxx 的所有半支配點中 dfn\text{dfn}dfn 最小的一個。
2.1.1.性質
semi(x)semi(x)semi(x) 是 xxx 在 dfs\rm dfsdfs 樹上的祖先。我盡量用簡潔的方式證明它。
首先,xxx 的父節點是 xxx 的半支配點,故
dfn[semi(x)]≤dfn(fax)<dfn(x)\text{dfn}[semi(x)]\le\text{dfn}(fa_x)<\text{dfn}(x) dfn[semi(x)]≤dfn(fax?)<dfn(x)
這是比較核心的關系。有了這個,考慮 semi(x)→xsemi(x)\rightarrow xsemi(x)→x 路徑上經過的第一個點 yyy(即路徑為 semi(x)?y→xsemi(x)\Rightarrow y\rightarrow xsemi(x)?y→x,顯然 yyy 是存在的,否則 semi(x)semi(x)semi(x) 已經是 xxx 的祖先),顯然有 dfn(y)>dfn(x){\rm dfn}(y)>{\rm dfn}(x)dfn(y)>dfn(x) 。
仔細思考一下,dfn(y)>dfn(x)>dfn[semi(x)]{\rm dfn}(y)>{\rm dfn}(x)>{\rm dfn}[semi(x)]dfn(y)>dfn(x)>dfn[semi(x)],那么 semi(x)?ysemi(x)\Rightarrow ysemi(x)?y 應當為祖先到兒子的邊。即 semi(x)semi(x)semi(x) 是 yyy 的祖先。
根據 1.2.2.dfn推論,semi(x)semi(x)semi(x) 至少要達到 lca(x,y)lca(x,y)lca(x,y) 才可以滿足 dfn[semi(x)]<dfn(x){\rm dfn}[semi(x)]<{\rm dfn}(x)dfn[semi(x)]<dfn(x),于是它就是 xxx 的祖先了。
2.1.2.求 semisemisemi
求 semi(x)semi(x)semi(x) 其實就是求半支配點。更新的時候,用 semisemisemi 更新(而非集合取并集)即可。
枚舉 semi(x)→xsemi(x)\rightarrow xsemi(x)→x 中 xxx 的前驅 yyy,如果 dfn(y)<dfn(x){\rm dfn}(y)<{\rm dfn}(x)dfn(y)<dfn(x),那就只能直接用 yyy 更新 semi(x)semi(x)semi(x) 了。否則,假設第一次走到 lca(x,y)→ylca(x,y)\rightarrow ylca(x,y)→y 的點為 zzz,顯然所有不到 lca(x,y)lca(x,y)lca(x,y) 的 yyy 的祖先都可以作為 zzz,也都可以用 semi(z)semi(z)semi(z) 更新 semi(x)semi(x)semi(x) 。
形式化的,候選 semisemisemi 點的集合為 SxS_xSx?,那么有
Sx=??y,x??z∈path(y,root)dfn(z)>dfn(x)SzS_x=\bigcup_{\langle y,x\rangle}\bigcup_{z\in path(y,root)}^{{\rm dfn}(z)>{\rm dfn}(x)}S_z Sx?=?y,x???z∈path(y,root)?dfn(z)>dfn(x)?Sz?
顯然 SzS_zSz? 是合法的,因為 semi(z)semi(z)semi(z) 到 zzz 的路徑上的點的 dfn>dfn(z)>dfn(x){\rm dfn}>{\rm dfn}(z)>{\rm dfn}(x)dfn>dfn(z)>dfn(x) 。不過它是否有所遺漏呢?即 dfn\rm dfndfn 介于 dfn(x){\rm dfn}(x)dfn(x) 和 dfn(z){\rm dfn}(z)dfn(z) 之間的點?
并沒有呢。那些點想要走到 zzz,那就是讓 dfn\rm dfndfn 增加,那么這樣的點必須是 zzz 的祖先。而 zzz 的祖先是不會先于 zzz 走到的!——比 lca(x,z)lca(x,z)lca(x,z) 更深的,根據定義,不應該在 zzz 之前訪問;比那更淺的,不能出現在路徑上,因為其 dfn\rm dfndfn 小于 dfn(x){\rm dfn}(x)dfn(x) 了。
2.2.支配點
在一張圖中設立一個源點 sss 。
若對于某一點 e(e≠s)e\;(e\ne s)e(e?=s) 滿足所有從 sss 到 eee 的路徑中都經過 uuu(即,在圖中刪去 uuu 之后,不存在 sss 到 eee 的路徑),則稱 uuu 為 eee 的支配點,或 uuu 支配 eee 。
顯然一個點的支配點可能有很多個。一個生動形象的例子是鏈。
2.3.支配樹
2.3.1.定義
對于一張圖和其源點 sss,支配樹是滿足該條件的樹:
- 包含圖中的每一個節點。
- 對于任意一點 u(u≠s)u\;(u\ne s)u(u?=s),其祖先(包括 uuu 本身)均為其支配點。
- 對于任意一點 u(u≠s)u\;(u\ne s)u(u?=s),其支配點均為其祖先(包括 uuu 本身)。
sss 會是樹的根節點。
2.3.2.存在性
我們要說明,為什么它會是一顆樹?就像 SAM\tt SAMSAM 要說明,為什么存在合法的自動機狀態轉移。
先證明一個小結論:若 vvv 到 uuu 的每一條路徑都經過 xxx,則存在一條從 xxx 到 uuu 的路徑不含 vvv 。
證明很簡單。用一點 C++\text{C++}C++ 的想法。反證法,若不成立,其函數應該像這樣:
void x_goto_u(){x_goto_v(); // 由假設,必須先到達vv_goto_u(); // 調用下面的函數 } void v_goto_u(){v_goto_x(); // 由假設,必須先到達xx_goto_u(); // 調用上面的函數 }這樣就死遞歸了呀!而路徑是客觀存在的,所以歸謬了。類似的,兩個點互相支配也是不可能的。
現在,我們可以說明一些支配點之間的性質。若 uuu 有兩個支配點 vvv 和 xxx,但是 vvv 和 xxx 沒有支配關系(即支配關系非 “樹形”),則存在兩條從 sss 到 uuu 的路徑,一個是 s→v→us\rightarrow v\rightarrow us→v→u,另一個是 s→x→us\rightarrow x\rightarrow us→x→u 。
對于前者,令 s→vs\rightarrow vs→v 不經過 xxx(由于 vvv 和 xxx 沒有支配關系,該路徑必然存在)。為了使 xxx 支配 uuu,必須讓 v→uv\rightarrow uv→u 總是經過 xxx 。根據上面的結論,x→ux\rightarrow ux→u 可以不經過 vvv,同理,s→xs\rightarrow xs→x 可以不經過 vvv,于是 s→x→us\rightarrow x\rightarrow us→x→u 完全不會經過 vvv,說明 vvv 不支配 uuu 啦,矛盾!
有了這一條,你會發現,一個點的所有支配點的導出子圖是完全圖。這個完全圖又不能存在有向環(否則存在兩個點互相支配,這是荒謬的),所以存在一個點被其他所有點支配。那么當前點就以這個點作為支配樹上的父節點即可。
有點歸納法的感覺,對嗎?對。按照 支配關系有向圖 的拓撲序建樹即可。
2.3.3.建支配樹
據說又是 tarjantarjantarjan 發明的算法……
我們要 semisemisemi 有什么用? 去掉所有非樹邊,連邊 ?semi(x),x?\langle semi(x),x\rangle?semi(x),x?,支配關系不改變!
其實這個原因挺簡單的。理解一下 semisemisemi 到底是什么:越過樹邊上的點。如果 uuu 是 vvv 的祖先,并且 u→vu\rightarrow vu→v,路徑上 不需要 有 dfn<dfn(v){\rm dfn}<{\rm dfn}(v)dfn<dfn(v) 的點——如果有,那其實還是 vvv 的祖先,沒有飛越樹上的點。
支配點肯定是 dfs\rm dfsdfs 樹上的祖先(否則樹邊構成一條路徑)。其樹上祖先,如果不是支配點,只可能是被跨過了。而跨過就是 semisemisemi 嘛。如果感性的理解一下,semisemisemi 已經建好了所有橋,不需要更多的邊了。
這樣有什么好處呢?至少有一個:原圖變為了 DAGDAGDAG!
其實 DAGDAGDAG 已經可以搞定了。對于所有前驅,求支配點交集,在支配樹上體現為 lcalcalca 的祖先。按照 dfn\rm dfndfn 排序后,使用帶權并查集,倍增維護 lcalcalca 。時間復雜度 O(nlog?n)O(n\log n)O(nlogn) 。
可是人總是精益求精的。令 idom(x)idom(x)idom(x) 為 xxx 的支配點中 dfn\text{dfn}dfn 最大的點,尋找 idom(x)idom(x)idom(x) 的方法是:
記 PPP 為從 semi(x)semi(x)semi(x) 到 xxx 的樹上路徑點集(不包括 semi(x)semi(x)semi(x) 本身),找一個 z∈Pz\in Pz∈P 滿足 dfn[semi(z)]\text{dfn}[semi(z)]dfn[semi(z)] 最小。
若 semi(z)=semi(x)semi(z)=semi(x)semi(z)=semi(x),則有 idom(x)=semi(x)idom(x)=semi(x)idom(x)=semi(x);否則有 idom(x)=idom(z)idom(x)=idom(z)idom(x)=idom(z) 。
對于前半句性感的證明就是,沒有 semi(x)semi(x)semi(x) 的祖先連到 PPP 中的邊,則刪去 semi(x)semi(x)semi(x) ,xxx 就不可達。畢竟 PPP 以內的所有點都連接 ?semi(p),p?\langle semi(p),p\rangle?semi(p),p? 之后也不能跨過 semi(x)semi(x)semi(x) 。別忘了 zzz 的定義哦!
對于后半句性感的證明(見下圖)就是:
假設刪掉 idom(z)idom(z)idom(z) ,xxx 依舊可達,則說明在 dfsdfsdfs 樹上,idom(z)idom(z)idom(z) 有一個祖先,可以走一條非樹邊(也就是通過 semisemisemi 連出來的邊,圖中紅邊)到達 xxx 到 idom(z)idom(z)idom(z) 中間的一個點 kkk 。
若 zzz 不是 kkk 的祖先,則刪掉 idom(z)idom(z)idom(z) 后 zzz 仍可達,與支配點定義不符,所以 zzz 是 kkk 的祖先。那么因為 z∈Pz\in Pz∈P ,所以 k∈Pk\in Pk∈P 。因為刪除 idom(z)idom(z)idom(z) 后 semi(z)semi(z)semi(z) 不可達,所以 dfn[semi(k)]<dfn[idom(z)]<dfn[semi(z)]\text{dfn}[semi(k)]<\text{dfn}[idom(z)]<\text{dfn}[semi(z)]dfn[semi(k)]<dfn[idom(z)]<dfn[semi(z)],與 zzz 的定義矛盾,所以該假設不可能成立。
版權聲明:本文為CSDN博主「litble」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/litble/article/details/83019578
這一小節是摘錄的,進行了部分修改。
2.4.代碼實現
我們要 idomidomidom 來干嘛呢?在支配樹上,xxx 的父親就是 idom(x)idom(x)idom(x) 唄。
發現 semisemisemi 需要用到 dfn\text{dfn}dfn 值更大的點的 semisemisemi 。那就按照 dfn\text{dfn}dfn 從大到小處理。
用 帶權并查集 好像可以做。用 mi(x)mi(x)mi(x) 存該點到根節點的路徑中,dfn[semi(z)]\text{dfn}[semi(z)]dfn[semi(z)] 值最小的一個 zzz 。畢竟也就用 yyy 的祖先中比 xxx 的 dfn\text{dfn}dfn 大的點唄!
求 idomidomidom 該怎么辦呢?它要用到 semi(x)semi(x)semi(x) 與 xxx 之間的信息。那就等到 semi(x)semi(x)semi(x) 處理完的時候處理吧!——那個時候,中間的所有點都處理了。
很巧的是,恰好此時 semi(x)semi(x)semi(x) 的祖先還沒處理!可以一并使用并查集,因為根節點就是 semi(x)semi(x)semi(x) 。
3.例題
3.1.板題
傳送門 to HDU
#include<bits/stdc++.h> using namespace std; #define RI register int int read() {int q=0;char ch=' ';while(ch<'0'||ch>'9') ch=getchar();while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();return q; } typedef long long LL; const int N=50005,M=100005; int n,m,tim; int dfn[N],repos[N],mi[N],fa[N],f[N],semi[N],idom[N],ans[N]; struct graph{int tot,h[N],ne[M],to[M];void clear() {tot=0;for(RI i=0;i<=n;++i) h[i]=0;}//此題數據有誤所以應從i=0開始清空void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;} }g,rg,ng,tr;void init() {tim=0;g.clear(),rg.clear(),ng.clear(),tr.clear();for(RI i=1;i<=n;++i)repos[i]=dfn[i]=idom[i]=fa[i]=ans[i]=0,mi[i]=semi[i]=f[i]=i; } void tarjan(int x) {dfn[x]=++tim,repos[tim]=x;for(RI i=g.h[x];i;i=g.ne[i])if(!dfn[g.to[i]]) fa[g.to[i]]=x,tarjan(g.to[i]); } int find(int x) {if(x==f[x]) return x;int tmp=f[x];f[x]=find(f[x]);if(dfn[semi[mi[tmp]]]<dfn[semi[mi[x]]]) mi[x]=mi[tmp];return f[x]; } void dfs(int x,LL num) {ans[x]=num+x;for(RI i=tr.h[x];i;i=tr.ne[i]) dfs(tr.to[i],num+x); } void work() {for(RI i=n;i>=2;--i) {int x=repos[i],tmp=n;for(RI j=rg.h[x];j;j=rg.ne[j]) {if(!dfn[rg.to[j]]) continue;//此題數據有誤if(dfn[rg.to[j]]<dfn[x]) tmp=min(tmp,dfn[rg.to[j]]);else find(rg.to[j]),tmp=min(tmp,dfn[semi[mi[rg.to[j]]]]);}semi[x]=repos[tmp],f[x]=fa[x],ng.add(semi[x],x);x=repos[i-1];for(RI j=ng.h[x];j;j=ng.ne[j]) {int y=ng.to[j];find(y);if(semi[mi[y]]==semi[y]) idom[y]=semi[y];else idom[y]=mi[y];//此時idom[mi[y]]可能并未找到}}for(RI i=2;i<=n;++i) {int x=repos[i];if(idom[x]!=semi[x]) idom[x]=idom[idom[x]];tr.add(idom[x],x);}dfs(n,0); } int main() {int x,y;while(~scanf("%d%d",&n,&m)) {init();for(RI i=1;i<=m;++i)x=read(),y=read(),g.add(x,y),rg.add(y,x);tarjan(n);work();for(RI i=1;i<n;++i) printf("%d ",ans[i]);printf("%d\n",ans[n]);}return 0; }4.后記
再次鳴謝:litble的感性理解支配樹,提供了思路與代碼!
支配樹為作者自學,如有錯誤之處,還請大佬斧正!
總結
- 上一篇: win7日常清理
- 下一篇: elasticsearch 关键词查询-