【模板】支配樹
看了許多大佬的講解,就用自己的語言說一遍,可能講得不好(畢竟還是太菜了)
題目大醫
給定一張有向圖,求從起點出發,求所有點再去掉的情況下有多少個點到達不了(就是支配點的概念)。
\(n\leq 2\cdot10^5\) , \(m\leq 3\cdot 10^5\)
前肢假想
根據題目,我們以此來建一個樹,稱之為“支配樹”
滿足“支配樹”要滿足這些性質:
1.這是一個樹(而不是圖),根節點就是起點;
2.對於任意點 \(u\) ,從根到它的簡單路徑上的所有點都是它的支配點;
3.對於任意點 \(u\) ,它支配它的子樹上所有的子孫節點。
如果能這樣造出來,問題就解決了。
前肢引入
\(Lengauer-Tarjan\)
哪裡都有 Tarjan 老爺子。。。 Tarjan orz !
\(Lengauer-Tarjan\) 演算法主要分了三個步驟:
1.從起點開始造 \(dfs\) 樹並求出所有點的 \(dfn\) ;
2.求出所有點的半支配點;(暫設陣列 \(semi(i)\) )
3.求出所有點的支配點。(暫設陣列 \(idom(i)\) )
其中半支配點是一個新概念,也是間接求支配點的精髓。
對於任意點 \(u\) 的半支配點表示所有點中有 dfn 最小的點 \(v\) ,能使 \(v\) 滿足有一條路徑能夠到達 u 且路徑上的所有點( \(u\) 和 \(v\) 都不算)的 \(dfn\) 都大於 \(dfn(u)\)
豬蹄內容
就只講講一般有向圖吧,因為這個解決了其他的就都解決了(如樹, DAG )。
絲綢之路的誕生之半支配點
設目前我們在研究點 \(u\) ,那麼再設點 \(v\) 指所有直接連向 \(u\) 的點。
分類討論(王道!)
1.\(dfn(v)=dfn(u)\)
直接無解。。跳過。
2.\(dfn(v)<dfn(u)\)
如果要從這條路徑上走的話,剛開始就結束了(根據定義來),所以把 \(v\) 和可能存在的 \(semi(u)\) 擇優更新就好了。
3.\(dfn(v)>dfn(u)\)
那麼就在 \(dfs\) 樹上從 \(v\) 找一個祖先 \(w\) 且 \(dfn(w)>dfn(u)\)
因為既然有了 \(semi(w)\) ,這一段路徑上所有 \(dfn\) 都大於 \(dfn(u)\) (定義來的), \(w\) 又是從 \(semi(u)\) 到 \(u\) 的路徑上搜到的,所以 \(w\) 之後的點也滿足。
(不是很懂??正常,我也有點雲裡霧裡的(就算明白了,一會也就忘了),但關鍵好背!!)
至此,半支配點完結!
絲綢之路的誕生之支配點
考慮在半支配點的肩膀上求出支配點。
設目前我們在研究點 \(u\) 。
然後記一個點集 \(\large\xi\) 表示在 \(dfs\) 樹上從 \(semi(u)\) 到 \(u\) 的路徑的點集,(但不包括 \(semi(u)\) )其中點 \(\alpha\) 是 \(\large\xi\) 中 \(dfn(semi)\) 最小的點。
那麼,分類討論(王道!)
(肯定是判斷 \(semi\) 相等而不是比較 \(dfn\) 大小了呀)
1.\(semi(\alpha)=semi(u)\)
則 \(idom(u)=semi(u)\) 。
因為 \(\alpha\) 已經是 \(semi(u)\) 到 \(u\) 路徑中所有點的下限( \(dfn(semi)\) )了,如若當前情況就是 \(\large\xi\) 成了個封閉的點集, \(semi(u)\) 是唯一直接相連的點,自然就有了 \(idom(u)=semi(u)\) 。
2.\(semi(\alpha)\neq semi(u)\)
則 \(idom(u)=idom(\alpha)\) 。
感性理解:
倘若有這麼一個“中間點” \(\beta\) 。
我們假設沒有了 \(idom(\alpha)\) , \(u\) 還可以能夠到達。那麼說明 \(idom(\alpha)\) 祖先會有一個點有一條非 \(dfs\) 樹邊連向了 \(idom(\alpha)\) 和 \(u\) 中間的一個點 \(\beta\) 。
其中, \(\alpha\) 必是 \(\beta\) 的祖先,否則沒有了 \(idom(\alpha)\) 也能到達 \(\alpha\) ,顯然與定義不符。
如若如此, \(\beta\) 便一定在 \(\large\xi\) 中了。
看得出來, \(semi\) 的要求比 \(idom\) 高一些,從滿足條件的難度上,所以 \(dfn(idom(\alpha))\leq dfn(semi(\alpha))\) 顯然。(記住,這是感性。。)
但 \(idom(\alpha)\) 的祖先有一條非 \(dfs\) 樹邊連向了 \(\beta\) ,所以無論如何 \(dfn(semi(\beta))<dfn(idom(\alpha))\) 。
結合上面的兩段內容,發現會與 \(\large\xi\) 關於 \(\alpha\) 的定義不符了。所以沒有了 \(idom(\alpha)\) , \(u\) 一定不能夠到達。
至此,支配點完結!
從絲綢之路到實際實現
1.半支配點
如果還有點印象的話,應該能知道求解過程都是 dfn 從大到小的逆推,所以可以倒序 dfn 處理。
對於第二個情況(第一個就。。)直接幹,而第三種情況,肯定是不會暴力列舉的。
於是考慮一個帶權並查集(說的好像要怎麼樣,但程式碼就是好背),每次處理完一個點,就往它在 dfs 樹上的點合併,按照支配點中 \(\large\xi\) 的要求維護對應的東西。
2.支配點
很多輔助東西都與半支配點一併整好了,只需要把已經求的 semi 與對應的點連邊,構造半支配樹,就能同時求解支配點。
由於可能存在搜尋的時候 idom 並未找到,可以先記錄下來,最後順序 dfn 再額外處理。
有一個小注意可見這位大佬在程式碼上方的講述。
時間複雜度 \(O(n\log_{2}{n})\) ,優雅的詮釋了\(Lengauer-Tarjan\) 演算法的優越性。
(沒辦法, Tarjan 老爺子就這麼厲害。。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10,M=3e5+10;
int n,m,ans[N],tot;
int fst[N][3],nxt[M+M+N],to[M+M+N];
int dfn[N],ord[N],cnt,fth[N];
int idom[N],semi[N],uni[N],mn[N];
inline ll read()
{
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline void add(int u,int v,int id)
{
nxt[++tot]=fst[u][id];
to[tot]=v,fst[u][id]=tot;
}
inline void Tarjan(int u)
{
ord[dfn[u]=++cnt]=u;
for(int i=fst[u][0];i;i=nxt[i])
{
int v=to[i];
if(!dfn[v])
{
fth[v]=u;
Tarjan(v);
}
}
}
inline int uni_query(int u)
{
if(u==uni[u])return u;
int tmp=uni_query(uni[u]);
if(dfn[semi[mn[u]]]>dfn[semi[mn[uni[u]]]])mn[u]=mn[uni[u]];
return uni[u]=tmp;
}
inline void Lengauer_Tarjan(int s)
{
Tarjan(s);
for(int i=1;i<=n;++i)semi[i]=uni[i]=mn[i]=i;
for(int id=cnt;id>=2;--id)
{
int u=ord[id];
for(int i=fst[u][1];i;i=nxt[i])
{
int v=to[i];
if(!dfn[v])continue;
uni_query(v);
if(dfn[semi[u]]>dfn[semi[mn[v]]])semi[u]=semi[mn[v]];
}
uni[u]=fth[u];
add(semi[u],u,2);
u=fth[u];
for(int i=fst[u][2];i;i=nxt[i])
{
int v=to[i];
uni_query(v);
idom[v]=(u==semi[mn[v]]?u:mn[v]);
}
fst[u][2]=0;
}
for(int i=2;i<=cnt;++i)
{
int u=ord[i];
if(idom[u]^semi[u])
idom[u]=idom[idom[u]];
}
}
int main()
{
n=read(),m=read();
for(int i=1;i<=m;++i)
{
int x=read(),y=read();
add(x,y,0),add(y,x,1);
}
Lengauer_Tarjan(1);
for(int i=cnt;i>=2;--i)ans[idom[ord[i]]]+=(++ans[ord[i]]);
++ans[1];
for(int i=1;i<=n;++i)printf("%d ",ans[i]);
return 0;
}
完結撒花!