Tarjan 求有向圖的強連通分量

TauLee01發表於2024-06-20

重溫Tarjan, 網上看了許多部落格感覺都講的不清楚. 故傳上來自己的筆記, 希望幫到大家.

提到的一些概念可以參考 oi wiki, 程式碼也是 oi wiki 的, 因為我不認為我能寫出比大佬更好的程式碼了.


強連通分量: 有向圖的最大強連通子圖 ( 有向圖中任意兩點可達 )

  • Tarjan

    1. 對每個結點維護:

      • dfn[x]: 當前節點的 dfs 序.

      • low[x]: x 向下搜尋能到達的最小 dfs 序.

    2. 更新 low:

      1. v 未被訪問過: 初始 low[v] = dfn[v].v 入棧. 回溯時用 low[v] 更新它的 fa 的 low[ ].

      2. v 被訪問過, 且還在棧中: 用 dfs[v] 更新 fa 的 low.

      3. v 被訪問過, 不在棧中: 說明這是一個 fa 到 v 的單向訪問, 跳過.

    3. 獲取答案:

      能讓 dfn[x] > low[x], 只有當 X 的子樹中某個節點 C 有\(\begin {cases}1.一條橫向邊連線到一棵已遍歷過的子樹~A\\2.一條返祖邊連線到~X~的祖先~xfa \end{cases}\) .

      1. 橫向邊: 說明 A 沒有連線到 C 的邊, 否則在之前 C 就被遍歷了, 輪不到 X 來遍歷. 就用是否 C 在棧中來排除這個情況, 子樹 A 中的所有強連通分量之前已經出棧過了( 看程式碼的實現 ).
      2. 返祖邊: 說明 xfa -> x -> c -> xfa 形成環, 在同一個強連通子圖( 我們知道, 強連通圖是許多環巢狀成的 ). 而且這個子圖的根是 xfa 滿足 dfn[xfa] = low[xfa].

      此時棧中進來過三類節點 :

      \[\begin {cases}1.~在~x~的子樹中\begin {cases}1.~屬於上述~xfa~迴圈的,~在同一個強連通子圖.\\2.~不在同一個強連通子圖,~那遞迴的講,~在之前就因為屬於某個~xfa'~(在~X~的子樹中),而被踢出棧了.\end{cases}\\2. 不在~x~的子樹中(即在已遍歷過的子樹中),~在棧中的位置一定在~x~的下面. \end{cases} \]

      故, 回溯時若節點符合 dfn[x] = low[x], 說明當前節點是它所屬連通塊的最小節點. 棧裡它之上所有點都是一個強連通塊.

程式碼:

 const int Maxn = 1e5 + 10;
    
    int dfn[Maxn], low[Maxn], dfncnt, s[Maxn], in_stack[Maxn], tp;
    int scc[Maxn], sc;  // 結點 i 所在 SCC 的編號
    int sz[Maxn];       // 強連通 i 的大小
    
    void tarjan(int u) {
        low[u] = dfn[u] = ++dfncnt, s[++tp] = u, in_stack[u] = 1;
        for (int i = head[u]; i; i = eg[i].nex) {
            const int &v = eg[i].to;
            if (!dfn[v]) {
                tarjan(v);
                low[u] = min(low[u], low[v]);
            } else if (in_stack[v]) {
                low[u] = min(low[u], dfn[v]);
            }
        }
        if (dfn[u] == low[u]) {
            ++sc;
            while (s[tp] != u) {
                scc[s[tp]] = sc;
                sz[sc]++;
                in_stack[s[tp]] = 0;
                --tp;
            }
            scc[s[tp]] = sc;
            sz[sc]++;
            in_stack[s[tp]] = 0;
            --tp;
        }
    }

相關文章