重溫Tarjan, 網上看了許多部落格感覺都講的不清楚. 故傳上來自己的筆記, 希望幫到大家.
提到的一些概念可以參考 oi wiki, 程式碼也是 oi wiki 的, 因為我不認為我能寫出比大佬更好的程式碼了.
強連通分量: 有向圖的最大強連通子圖 ( 有向圖中任意兩點可達 )
-
Tarjan
-
對每個結點維護:
-
dfn[x]
: 當前節點的 dfs 序. -
low[x]
: x 向下搜尋能到達的最小 dfs 序.
-
-
更新 low:
-
v 未被訪問過: 初始
low[v] = dfn[v]
.v 入棧. 回溯時用 low[v] 更新它的 fa 的 low[ ]. -
v 被訪問過, 且還在棧中: 用 dfs[v] 更新 fa 的 low.
-
v 被訪問過, 不在棧中: 說明這是一個 fa 到 v 的單向訪問, 跳過.
-
-
獲取答案:
能讓
dfn[x] > low[x]
, 只有當 X 的子樹中某個節點 C 有\(\begin {cases}1.一條橫向邊連線到一棵已遍歷過的子樹~A\\2.一條返祖邊連線到~X~的祖先~xfa \end{cases}\) .- 橫向邊: 說明 A 沒有連線到 C 的邊, 否則在之前 C 就被遍歷了, 輪不到 X 來遍歷. 就用是否 C 在棧中來排除這個情況, 子樹 A 中的所有強連通分量之前已經出棧過了( 看程式碼的實現 ).
- 返祖邊: 說明 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;
}
}