tarjan複習小結
雖然是複習,但還是學到許多。
tarjan求強連通分量
基礎知識
過程中遇到四種邊
1>樹枝邊:\(dfs\)搜尋樹上的邊 滿足邊\((u,v) ,v\)不在棧中 \(u\)為\(v\)的父節點
2>前向邊:與\(dfs\)方向一致 祖先指向子孫 沒什麼用
3>後向邊:與\(dfs\)方向相反 子孫指向祖先 滿足邊\((u,v)\). \(v\)在棧中,\(u\)為\(v\)的祖先節點
4>橫叉邊:從某個結點指向搜尋樹中另一子樹中某結點的邊 滿足邊\((u,v)\), \(v\)在棧中 \(u\)不為\(v\)的祖先節點
定義兩個陣列\(dfn\)和\(low\)
如果\((u,v)\)是樹枝邊 \(low[u]=min(low[u],low[v])\)
如果是橫叉邊或後向邊 \(low[u]=min(dfn[v],low[u])\)
當結點\(u\)的搜尋過程結束之後,若\(dfn[u] == low[u]\) 則以\(u\)為根的搜尋子樹上所有還在棧中的結點,是一個強連通分量,可退棧,為什麼呢????
我也不知道
通俗的說,若\(u\)為強連通分量的根,那麼它的子孫中的最高祖先應該是它本身。
演算法流程
陣列的初始化,當首次到達\(u\)這個點時,更新\(low\)與\(dfn\)的值, 將\(u\)入棧
更新\(low[u]\)
如果\((u,v)\)是樹枝邊 \(low[u]=min(low[u],low[v])\)
如果是橫叉邊或後向邊 \(low[u]=min(dfn[v],low[u])\)
如果u的子樹全被遍歷完,\(low[u] == dfn[u]\)那麼退棧,退到\(u\)退出,這些退出的元素就是一個強連通分量。
因為圖有可能有好幾個部分,也就是說,\(tarjan\)圖不連通。那麼我們還要繼續搜尋,直到所有點都被遍歷
code
void tarjan(int x) { low[x] = dfn[x] = ++cnt, sta[++top] = x, vis[x] = 1; for (int i = head[x]; i; i = edge[i].next) { int to = edge[i].to; if (!dfn[to]) tarjan(to), low[x] = min(low[x], low[to]); else if (vis[to]) low[x] = min(dfn[to], low[x]); } if (dfn[x] == low[x]) { num++; while (x != sta[top + 1]) { vis[sta[top]] = 0; top--; } } } for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
模型建立
其實就是縮點,要不然也沒別的用處。
因為每一個強連通分量中的點都是相互可達的,我們可以將當前這個強連通分量縮成一個點,這個點的權值由題目來定(通常會有強連通分量中的點權和或者點權的最小值)
我們用到染色的思想,因為退棧的時候退棧的所有元素都是同一個強連通分量中的,所以我們可以在這個時將所有的點都染成同一種顏色,同時處理縮完點之後點的權值。
code
void tarjan(int x) {
low[x] = dfn[x] = ++cnt, sta[++top] = x, vis[x] = 1;
for (int i = head[x]; i; i = edge[i].next) {
int to = edge[i].to;
if (!dfn[to]) tarjan(to), low[x] = min(low[x], low[to]);
else if (vis[to]) low[x] = min(dfn[to], low[x]);
}
if (dfn[x] == low[x]) {
num++;
while (x != sta[top + 1]) {
vis[sta[top]] = 0;
col[sta[top]] = num;
val[num] += a[sta[top]];
top--;
}
}
}