圖論學習筆記——強連通分量/雙連通分量
阿新 • • 發佈:2020-10-14
預備知識
- 強連通:對於圖\((V,E)\)上的兩個節點\(u,v\),若存在從\(u\)到\(v\)的有向邊,同時也存在從\(v\)到\(u\)的有向邊,則稱這兩個點強連通。
- 強連通圖:若圖\((V,E)\)上的任意兩個節點都強連通,則稱這個圖為強連通圖。
- 強連通分量:有向圖的極大強連通子圖
強連通分量(SCC,Strongly Connected Components)
求強連通分量(Tarjan演算法)
\(dfn[i]:=\) 節點\(i\)在搜尋過程中的次序編號(時間戳),即記錄節點\(i\)是第幾個被搜尋到的節點。
\(low[i]:=\) 節點\(u\)及其後代節點所能追溯到的最早的節點(即祖先節點)\(v\)
low[i]=dfn[i]
.
int dfs_clock, scc_cnt; int dfn[maxn], low[maxn], sccno[maxn]; void dfs(int u) { dfn[u] = low[u] = ++dfs_clock; //給節點u打上時間戳 s.push(u); for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (!dfn[v]) { //節點v還沒被搜尋到 dfs(v); low[u] = min(low[u], low[v]); //維護祖先中最小的時間戳 } else if (!sccno[v]) { low[u] = min(low[u], dfn[v]); //節點v已經被搜尋過了,但還不屬於某一個SCC } } //對節點u的所有後代節點完成搜尋之後 //開始判斷節點u是不是這個強連通分量中第一個出現的節點 if (low[u] == dfn[u]) { scc_cnt++; //SCC數量+1 while (1) { int x = s.top(); s.pop(); sccno[x] = scc_cnt; //給分量中的所有節點記錄所在SCC的編號 if (x == u) break; //訪問完u之後,就完成了對這個SCC所有節點的訪問,跳出 } } } void find_scc(int n) { dfs_clock = scc_cnt = 0; mem(sccno, 0); mem(dfn, 0); for (int i = 1; i <= n; i++) { if (!dfn[i]) dfs(i); } }
縮點
顧名思義,將圖中的強連通分量看作是一個點,就是縮點。同時原圖變為一個DAG,如此便可以將一個有環的圖轉化為DAG,可以利用DAG的性質解決問題。
問題一:給定一個有向圖\((V,E)\),包含\(n\)個點和\(m\)條邊,問至少還要再新增多少條邊才能使整個圖變成強連通圖。
對於DAG,這個問題的答案是\(max(a,b)\),其中\(a\)是入度為零的節點個數,\(b\)是出度為零的節點個數。特別地,如果DAG中只有一個點,則答案為0。但問題中給出的圖不一定無環,此時就可以用縮點的方法將圖轉化為DAG。
問題二:給定一個有向圖\((V,E)\),包含\(n\)個點和\(m\)條邊,每個點有一個權值。求一條路徑,使得路徑上點的權值和最大。允許多次經過一條邊或一個點,但權值只計算一次。
對於DAG,這個問題就是求DAG上的最長路,用DAG上的dp即可解決。定義\(dp[i]\)為從節點\(i\)出發的路徑的最大權值和,則轉移方程為\(dp[i]=max(dp[i],dp[j]+val[i])\),其中節點\(j\)滿足:存在\(i→j\)的有向邊。
需要注意的是,在方程中需要先求出\(dp[j]\),才能用它來更新\(dp[i]\)。具體程式碼實現有兩種方法:
- 記憶化搜尋;
- 先求圖的拓撲排序,再以拓撲排序的倒序進行dp。
題目給定的圖可能有環,只需對原圖求強連通分量,縮點,建立新圖,則新圖就是DAG。