tarjan提出了不少算法.本文討論的是圖論中求解強連通份量的那個tarjan算法...的應用。c++
講得不會很是基礎,甚至只是起到記錄知識的做用.算法
建議先閱讀他人的文章,在對tarjan算法有了大概瞭解後再繼續讀下去.spa
本文討論的核心是.net
有向圖爲什麼要縮點code
什麼是有向圖縮點blog
有向圖縮點的實現細節圖片
hihoCoder-1185 的實現代碼ci
本文暫時(也多是永久)不涉及get
tarjan算法的正確性證實it
tarjan算法的理解
考慮一個有向圖,起始點爲1,每一個點用正整數編號.給出連通關係以及各節點權值.權值都是天然數.
問: 從1點出發,終點隨意,最大路上節點權和能夠是多少?
例如: 1->2 就是個合法路徑. 這個路徑的權值和是6.
如[1]所示.方括號內的數字是此節點的權值.
先考慮暴力算法,DFS.但直接搜下去會死循環.
爲何呢?這是由於這個有向圖中有環.
3->6
和 6->3
這兩條邊使得3和6處在一個環內.兩個點強連通.
若是不加處理地從1點開始DFS,一定會在3和6之間來回搜索,由於這樣路上的節點權和就會無限增加.
那麼,把全部的"環"都收縮爲一個點,那就成了有向無環圖(DAG)了.該DP就DP,該DFS就 DFS,毫無困擾了.
縮點以後是這樣的:
這就把6號點"合併"進了3號點.合併以後,3號點和6號點之後等價.
爲了方便,咱們用3號點來"表明"6號點和環內其餘的點,若是有的話
.
這個思想和並查集中"表明元"的思想很像.而且咱們約定:編號爲n
的節點的"表明元"是contract[n]
.若是節點n
不在環中,爲了通常性,令contract[n] = n
,即本身的表明元爲本身.這與並查集的約定相同.
將一堆點"合併"爲一個"表明元"要修改的有兩個東西.一個是邊,一個是權.
修改邊和權,具體實現起來有兩種風格.
修改現成的圖
從新建圖
筆者喜歡修改圖.從新建圖是弱者的行爲(笑).
首先,用tarjan算法找出從1點出發能到達的全部環...固然每次只會找出一個環.咱們說過,每一個環都有且僅有一個"表明元".
接着對於這個環上的每一個非表明元節點,
把它的全部出邊複製給"表明元"後刪除.
把它的權值加到表明元上.
你可能會想到: 出邊所有刪除了,那"其餘節點"進入環中非表明元節點的邊怎麼辦呢?換言之,非表明元節點的入邊怎麼解決?
很簡單,對於圖中的每條入邊指向的節點編號k
,都令其等於contract[k]
.
正所謂"有則改之無則加勉"(逃
//AC, 116ms #include<bits/stdc++.h> #define ll long long using namespace std; const int maxn = 2e4 + 100; vector<int> e[maxn]; int ins[maxn], dfn[maxn], low[maxn], contract[maxn]; ll w[maxn]; int ind; stack<int> s; void tarjan(int u) { dfn[u] = low[u] = ++ind; ins[u] = 1; s.push(u); for(int i = 0; i < e[u].size(); i++) { int v = e[u][i]; if(!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if(ins[v]) low[u] = min(low[u], dfn[v]); } if(dfn[u] == low[u]) { int v; do { v = s.top(); s.pop(); ins[v] = 0; contract[v] = u; if(u != v) { w[u] += w[v]; while(!e[v].empty()) { e[u].push_back(e[v].back()); e[v].pop_back(); } } } while(u != v); } } ll dfs(int u, ll cnt) { cnt += w[u]; ll re = cnt; for(int i = 0; i < e[u].size(); i++) { int v = contract[e[u][i]]; if(v != u) re = max(re, dfs(v, cnt)); } return re; } int main() { int n, m; cin >> n >> m; for(int i = 1; i <= n; i++) { cin >> w[i]; } for(int i = 1; i <= m; i++) { int u, v; cin >> u >> v; e[u].push_back(v); } tarjan(1); cout << dfs(1, 0) << endl; return 0; }
由於這份代碼用的是vector<int>[]
的鄰接表存邊法,因此效率並非十分高.
各類細節一如前文所述.
之後有空再加一份用鏈式前向星(咱們一般叫作鏈表)作鄰接表的代碼.