完結篇:tarjan 求割點、點雙連通分量、割邊(橋)(附 40 道很好的 tarjan 題目)。
上一篇(tarjan 求強連通分量,縮點,求邊雙)
tarjan 求割點
還是求強聯通分量的大致思路捏.
演算法思路:
我們把圖中的點分為兩種: 每一個聯通子圖搜尋開始的根節點 和 其他點。
判斷是不是割點的方式如下:
-
對於根節點:
記一下在跑 tarjan 過程中從這個點出發的未被搜尋到的子節點的數量 \(child\),如果 \(child\ge 2\),那麼這個點為割點,否則就不是割點;- 證明(很簡單,建議自己先想一想為什麼或者手模個圖,實在不懂再看證明):
設根節點為點 \(u\),如果 \(child\) 為 1 的話,說明根節點的不同子樹上的點可以不經過點 \(u\) 而互相到達,也就是說即使刪了 \(u\) 點,圖中所有點照樣可以互相到達,則 \(u\) 不為割點。反之,同樣也易證得為割點。
-
對於其他點:
判斷一個點 \(u\) 有沒有一個子節點 \(v\) 使得low[v] >= dfn[u]
,若存在,則 \(u\) 為割點,否則不為割點。- 證明:
因為我們在無向圖中跑 tarjan 時,已經特判父節點或邊的編號來避免走“回頭路”了(詳見上文求邊雙部分),所以如果滿足判斷條件時,說明 \(v\) 透過返祖邊只能到達 \(u\) 及其子樹部分,並不能到達 \(u\) 的祖先節點,所以若 \(u\) 點刪去,那麼 \(v\) 點便與 \(u\) 以上部分斷開了,此時顯然 \(u\) 為割點。
會判斷這兩類點是不是割點之後就做完了,相信大家也知道該怎麼求了。看程式碼吧!
演算法程式碼:
相比於求邊雙部分增加了陣列 cut[x]
來判斷 \(x\) 是不是割點,若為 true 則 \(x\) 是割點,否則不是。
void tarjan(int x, int p){
low[x] = dfn[x] = ++th;
s[++top] = x; int child = 0;
for(int i=head[x]; i; i=nxt[i]){
int y = to[i];
if(y == p) continue;
if(!dfn[y]){
tarjan(y, x);
low[x] = min(low[x], low[y]);
if(low[y] >= dfn[x]){
child++;
if(p != 0 or child > 1) cut[x] = 1;
}
}
else low[x] = min(low[x], dfn[y]);
}
}
為什麼 p == 0
說明 \(x\) 為根節點呢,大家肯定知道啦!
因為主函式中是這麼寫的:
for(int i=1; i<=n; i++)
if(!dfn[i]) tarjan(i, 0);
tarjan 求點雙連通分量
我該先寫求點雙好呢還是先寫求割邊好呢,這倆都是需要割點的相關知識的,啊選擇困難症(
但是的但是,學會求割點之後那求點雙(簡稱 BCC)就很簡單啦 啦啦小魔仙,瑪卡巴卡,卡巴露露,搖身變!
演算法思路:
性質:無向連通圖中割點一定屬於至少兩個BCC,非割點只屬於一個BCC
如此圖:2 號點是個割點,其他點則不是。有紅、藍兩個 BCC。
有一條顯然的結論:每個點雙,它在 dfs 時最先被發現的點一定是割點或者 dfs 樹的樹根。
證明:這很顯然吧?!根據割點的定義自己理解一下,不證明。算了,還是簡單說一下吧:我們知道割點是 BCC 的交點,即 BCC 透過割點連線,從一個 BCC 到另一個 BCC 一定是從經過割點開始的,所以證得。
那麼這條結論其實就等價於 每個 BCC 都在其最先被發現的點(一個割點或根節點)的子樹中。那麼我們在上文求割點方法基礎上每找到一個割點(或根節點)後,其子樹(包含自己)便是一個點雙連通分量了。
實現:
我們還是維護一個棧,存點,每當搜尋到一個點時就將該點入棧,找到割點(就是找到一個 BCC)時將棧頂到該割點所有元素依次出棧,(但注意:割點並不出棧,因為上文已說一個割點屬於兩個 BCC,它還需要來更新另一個 BCC,所以先不出棧,特判就行。)那麼出棧的元素以及割點就是所求的點雙了。
演算法演示:
如上圖中,我們以 1 為根開始搜尋;
搜尋到 2 節點時,繼續遞迴 2 -> 3 -> 4;發現 \(low_4 = 2 < dfn_3\),那麼 3 號點則不是割點,回溯;
而 \(low_3 = dfn_2\),所以 \(2\) 號點是割點,那麼將此時棧中從棧頂到 \(2\) 號點所有元素出棧形成點雙;
此時棧從棧尾到棧頂依次是:1,2,3,4。那麼便是 2,3,4 構成一個點雙(但 2 還在棧中)。
繼續回溯到 2 -> 1;發現 1 號點是根節點,也將棧中元素出棧(這時 1 是根節點,所以 1 也出棧),那麼 1,2 就又構成了一個點雙。
演算法程式碼:
void tarjan(int x, int p){
low[x] = dfn[x] = ++th;
s[++top] = x;
if(!p and !head[x]){ // 特判孤點
BCC[++bcc].emplace_back(x);
top--; return;
}
for(int i=head[x]; i; i=nxt[i]){
int y = to[i];
if(y == p) continue;
if(!dfn[y]){
tarjan(y, x);
low[x] = min(low[x], low[y]);
if(low[y] >= dfn[x] or !p){ //是割點或者根節點
++bcc;
do BCC[bcc].emplace_back(s[top]);
while(s[top--] != y);
BCC[bcc].emplace_back(x);
}
}
else low[x] = min(low[x], dfn[y]);
}
}
例題:
【模板】點雙連通分量
板子題練練手。注意這題需要判孤點情況。
tarjan 求割邊(橋)
太簡單啦!
和割點差不多,改一條:low[y] > dfn[x]
,並且不需要特判根節點了(因為 邊 != 點)。
解釋:(在判邊保證不走回頭路的條件下)\(low_y = dfn_x\) 時,說明不透過從 \(x -> y\) 這條路徑, \(y\) 也照樣可以回到 \(x\) 節點,那麼就保證從 \(y\) 到 \(x\) 有兩條路徑可走了,所以 \(x->y\) 這條路不是割邊。
那麼完了!
演算法程式碼:
cut[i] = true;
表示 \(i\) 這條路為割邊。
void tarjan(int x, int p){
low[x] = dfn[x] = ++th;
s[++top] = x;
for(int i=head[x]; i; i=nxt[i]){
int y = to[i];
if(y == p) continue;
if(!dfn[y]){
tarjan(y, x);
low[x] = min(low[x], low[y]);
if(low[y] > dfn[x]){
cut[i] = true;
}
}
else low[x] = min(low[x], dfn[y]);
}
}
題目練習:
這有個很好的 tarjan 題單,從模板到進階,題都很好,推薦給大家。
所含題目如下
P1656 炸鐵路
P1455 搭配購買
P3916 圖的遍歷
P2835 燒錄光碟
P1073 [NOIP2009 提高組] 最優貿易
P2863 [USACO06JAN]The Cow Prom S
P8436 【模板】邊雙連通分量
P8287 「DAOI R1」Flame
P2002 訊息擴散
P2341 [USACO03FALL / HAOI2006] 受歡迎的牛 G
P3387 【模板】縮點
P3388 【模板】割點(割頂)
P8435 【模板】點雙連通分量
P1407 [國家集訓隊]穩定婚姻
P2194 HXY燒情侶
P2746 [USACO5.3]校園網Network of Schools
P2812 校園網路【[USACO]Network of Schools加強版】
P2941 [USACO09FEB]Surround the Islands S
P2860 [USACO06JAN]Redundant Paths G
P3398 倉鼠找 sugar
P2169 正規表示式
P3627 [APIO2009] 搶掠計劃
P2656 採蘑菇
P4306 [JSOI2010]連通數
P5676 [GZOI2017]小z玩遊戲
P1656 炸鐵路
P1455 搭配購買
P3916 圖的遍歷
P2835 燒錄光碟
P1073 [NOIP2009 提高組] 最優貿易
P2863 [USACO06JAN]The Cow Prom S
P8436 【模板】邊雙連通分量
P8287 「DAOI R1」Flame
P2002 訊息擴散
P2341 [USACO03FALL / HAOI2006] 受歡迎的牛 G
P3387 【模板】縮點
P3388 【模板】割點(割頂)
P8435 【模板】點雙連通分量
P1407 [國家集訓隊]穩定婚姻
P2194 HXY燒情侶
P2746 [USACO5.3]校園網Network of Schools
P2812 校園網路【[USACO]Network of Schools加強版】
P2941 [USACO09FEB]Surround the Islands S
P2860 [USACO06JAN]Redundant Paths G
P3398 倉鼠找 sugar
P1262 間諜網路
P4742 [Wind Festival]Running In The Sky
P8867 [NOIP2022] 建造軍營
P3469 [POI2008]BLO-Blockade
P2515 [HAOI2010]軟體安裝
P5058 [ZJOI2004]嗅探器
P7687 [CEOI2005] Critical Network Lines
P7924 「EVOI-RD2」旅行家
P5236 【模板】靜態仙人掌
P3225 [HNOI2012]礦場搭建
P4716 【模板】最小樹形圖
P4126 [AHOI2009]最小割
P6335 [COCI2007-2008#1] STAZA
P4637 [SHOI2011]掃雷機器人
P5236 【模板】靜態仙人掌
P4716 【模板】最小樹形圖
P4436 [HNOI/AHOI2018]遊戲
End
歷時多天,終於把這兩篇 tarjan 寫完了。
tarjan 都學了,那下一章包得是圓方樹的啦(
\(敬請期待......\)