淺談支配樹
前言
這個神奇的東西我也是最近才聽說,感覺挺神仙的,然後去寫了一下模板題,再發一篇部落格加深記憶。
支配點與半支配點
我們定義一個有向圖中結點x關於結點s支配結點y,當且僅當在所有s通往y的路徑中都經過了x,我們也稱x是y關於s的支配點。
一個簡單的問題是我們要找出對於任意一個點x找出支配它的點的個數。
首先這個暴力肯定是$O(n^3)$的,但是有一個Lengauer-Tarjan演算法可以在$O(m\log_{1+m/n} n)$的時間複雜度內做出這個東西,我們可以來學習一下。
首先引入一個概念:半支配點。
我們先建立一棵根為s的dfs樹,記點x的dfs序為$dfn(x)$,然後我們稱y是點x的關於s的半支配點,當且僅當y存在一條通往x的路徑,且對於路徑上除y以外的所有點z,都有$dfn(z)\ge dfn(x)$。
對於點x,其dfs序最小的半支配點我們記為$semi(x)$,dfs序最大的支配點我們記為$idom(x)$。
顯然$semi(x)$與$idom(x)$均為x在dfs樹上的祖先。
另外可以證明,支配具有傳遞性,如果A支配了B,B支配了C,那麼A也支配了C。所以,如果我們以$idom(x)$作為x的父親建立一棵樹,那麼這棵樹的一棵子樹中的節點個數為該子樹根支配的點的個數,能支配某一個點的點的個數是該節點到根節點上的節點個數。
Lengauer-Tarjan演算法
這是一個很神奇的建樹方法,大概意思就是先求semi,再用semi求idom。
首先我們建出dfs樹,考慮如何求semi。
對於節點x,考慮這條路徑的上一個節點是什麼,我們先建出反圖,找到所有存在邊$y->x$的y,然後假如$dfn(y)<dfn(x)$那麼y一定是x的祖先,直接轉移一下就好了。
難點在於$dfn(y)>dfn(x)$的情況,我們需要找到所有路徑中dfs序大於$dfn(x)$且能通過y到達x的路徑起點,我們考慮按dfs序從大到小做,那麼此時y的semi已經求出來了,路徑上除了x的祖先以外可能有的節點的semi也已經求出來了,所以只需要求當前y到最高能到達的祖先這條路徑上節點的$dfn(semi(z))$最小的節點,這個我們可以用一個並查集維護,把一棵子樹做完之後把根的所有兒子併到根上。在路徑壓縮的時候順帶維護一下某個節點到當前根的路徑上我們需要維護的值。
需要注意的是,我們的並查集不能按秩合併,所以時間複雜度不是攤還$O(\alpha(n))$的。它大概長這樣吧:
struct DSF{ int fa[MAX_N],key[MAX_N]; int find_set(int x){ if(fa[x]!=x){ int p=fa[x]; fa[x]=find_set(p); if(dfn[semi[key[p]]]<dfn[semi[key[x]]]) key[x]=key[p]; } return fa[x]; } void link(int x,int y){ fa[y]=x; } void make_set(int x){ fa[x]=x,key[x]=x; } int get_key(int x){ //用於求出路徑上semi值最小的結點 find_set(x); return key[x]; } }dsf;
這個是求semi的程式碼(乾脆把兩種情況並在一起了):
//G2是反圖,G3是dfs樹 for(int j=G2.head[x];j!=-1;j=G2.edge[j].nxt){ int y=G2.edge[j].to; if(dfn[y]==0) continue; y=dsf.get_key(y); if(dfn[semi[y]]<dfn[semi[x]]) semi[x]=semi[y]; } for(int j=G3.head[x];j!=-1;j=G3.edge[j].nxt){ //做完之後合併一下 int y=G3.edge[j].to; dsf.link(x,y); }
是用$semi$求$idom$,考慮$x$到$semi(x)$這段路徑,如果裡面$semi$一個都沒有超過$semi(x)$那麼顯然$idom(x)=semi(x)$,
如果有呢,那麼就是$x$到$semi(x)$這段路徑上$dfn(semi(x))$最小的節點的$idom(x)$(證明很簡單,分類討論一下就好了)。
但是我們在做的時候怎麼求呢,我們再開一個邊表,從x往$semi(x)$連一條邊,然後還是用之前那個並查集維護出路徑上$dfn(semi(z))$最小的點,但是這個時候我們發現$idom(semi(z))$可能還沒有被求過,那麼我們就記錄一下,然後最後統一更新答案就好了。
程式碼很短。
if(x!=s) G4.add_edge(semi[x],x); for(int j=G4.head[x];j!=-1;j=G4.edge[j].nxt){ int y=G4.edge[j].to; int z=dsf.get_key(y); if(semi[z]==semi[y]) idom[y]=semi[y]; else idom[y]=z; }
例題
這個由於剛剛進入OI領域不久所以只有模板題。
洛谷模板題程式碼:
#include<bits/stdc++.h> using namespace std; const int MAX_N=5+2e5; struct Graph{ struct Edge{ int to,nxt; }edge[MAX_N*2]; int head[MAX_N],top_edge; Graph(){ top_edge=-1,memset(head,-1,sizeof(head)); } void add_edge(int x,int y){ edge[++top_edge]=(Edge){y,head[x]}; head[x]=top_edge; } }G1,G2,G3,G4,G5; int cnt[MAX_N],semi[MAX_N],idom[MAX_N],ti=0; int dfn[MAX_N],id[MAX_N],fa[MAX_N]; struct DSF{ int fa[MAX_N],key[MAX_N]; int find_set(int x){ if(fa[x]!=x){ int p=fa[x]; fa[x]=find_set(p); if(dfn[semi[key[p]]]<dfn[semi[key[x]]]) key[x]=key[p]; } return fa[x]; } void link(int x,int y){ fa[y]=x; } void make_set(int x){ fa[x]=x,key[x]=x; } int get_key(int x){ find_set(x); return key[x]; } }dsf; void dfs(int x,int pre){ dfn[x]=++ti,id[ti]=x,semi[x]=x,fa[x]=pre; for(int j=G1.head[x];j!=-1;j=G1.edge[j].nxt){ int y=G1.edge[j].to; if(dfn[y]==0){ G3.add_edge(x,y); dfs(y,x); } } } void solve(int n){ idom[1]=1; for(int i=n;i>=1;--i){ int x=id[i]; if(dfn[x]==0) continue; for(int j=G2.head[x];j!=-1;j=G2.edge[j].nxt){ int y=G2.edge[j].to; if(dfn[y]==0) continue; y=dsf.get_key(y); if(dfn[semi[y]]<dfn[semi[x]]) semi[x]=semi[y]; } if(x!=1) G4.add_edge(semi[x],x); for(int j=G4.head[x];j!=-1;j=G4.edge[j].nxt){ int y=G4.edge[j].to; int z=dsf.get_key(y); if(semi[z]==semi[y]) idom[y]=semi[y]; else idom[y]=z; } for(int j=G3.head[x];j!=-1;j=G3.edge[j].nxt){ int y=G3.edge[j].to; dsf.link(x,y); } } for(int i=2;i<=n;++i){ int x=id[i]; if(dfn[x]==0) continue; idom[x]=idom[x]==semi[x]?semi[x]:idom[idom[x]]; G5.add_edge(idom[x],x); } } void dfs2(int x){ cnt[x]=1; for(int j=G5.head[x];j!=-1;j=G5.edge[j].nxt){ int y=G5.edge[j].to; dfs2(y); cnt[x]+=cnt[y]; } } int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=m;++i){ int x,y; scanf("%d%d",&x,&y); G1.add_edge(x,y); G2.add_edge(y,x); } for(int i=1;i<=n;++i) dsf.make_set(i); dfs(1,0); solve(n); dfs2(1); for(int i=1;i<=n;++i) printf("%d ",cnt[i]); return 0; }