一文輕鬆搞定 tarjan 演算法(二)

Aqr_Rn發表於2024-09-10

完結篇: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 都學了,那下一章包得是圓方樹的啦(

\(敬請期待......\)

相關文章