雖然是複習,但還是學到許多。
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\),\(dfn[x]\)表示\(x\)節點是第幾個被遍歷到的。\(low[x]\)表示\(x x\)以及它的所有子樹的出邊的\(dfn\)的最小值,由定義可以得出:
如果\((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--;
}
}
}
例題
間諜網絡
穩定婚姻
消息擴散
HXY燒情侶
受歡迎的牛
校園網
最大半連通子圖
網絡協議
消息的傳遞
間諜網絡