更壞的閱讀體驗html
對於給定的一個網絡,有向圖中每一個的邊權表示能夠經過的最大流量。假設出發點S水流無限大,求水流到終點T後的最大流量。ios
起點咱們通常稱爲源點,終點通常稱爲匯點算法
在一個網絡從源點S
到匯點T
的一條各邊剩餘流量都大於0(還能讓水流經過,沒有堵住)的一條路。數組
預處理出源點到每一個點的距離(每次尋找增廣路都要,由於之前本來能走的路可能由於水灌滿了,致使不能走了).做用是保證只往更遠的地方放水,避免兜圈子或者是沒事就走回頭路(正所謂人往高處走水往低處流).網絡
每次增廣一條路後能夠看作「榨乾」了這條路,既然榨乾了就沒有再增廣的可能了。但若是每次都掃描這些「枯萎的」邊是很浪費時間的。那咱們就記錄一下「榨取」到那條邊了,而後下一次直接從這條邊開始增廣,就能夠節省大量的時間。這就是當前弧優化函數
具體怎麼實現呢,先把鏈式前向星的head數組複製一份,存進cur數組,而後在cur數組中每次記錄「榨取」到哪條邊了。學習
[#3 引用自](Dinic當前弧優化 模板及教程 - Floatiy - 博客園 (cnblogs.com))優化
FF算法的核心是找增廣路,直到找不到爲止。(就是一個搜索,用盡量多的水流填充每個點,直到沒有水用來填充,或者沒有多餘的節點讓水流出去)。spa
可是這樣的方法有點基於貪心的算法,找到反例是顯而易見的,不必定能夠獲得正解。code
爲了解決這種問題,咱們須要一個能夠吃後悔藥的方法——加反向邊。
本來咱們的DFS是一條路走到黑的,如今咱們每次進入一個節點,把水流送進去,同時創建一個權值與咱們送入的水流量相等,可是方向相反的路(挖一條路讓水流可以反向流回來,至關於給水流吃一顆後悔藥)。
咱們給了FF算法一顆後悔藥以後就可讓他可以找到正確的最大流。
Ford-Fulkerson算法的複雜度爲\(O(e \times f)\) ,其中 \(e\) 爲邊數, \(f\)爲最大流
上代碼。
#include <iostream> #include <cstring> using namespace std; #define INF 0x3f3f3f3f3f3f3f3f typedef long long ll; // Base const int N= 256; const int M= 8192*2; // End // Graph int head[N],nxt[M],to[M]; ll dis[M]; int p; inline void add_edge(int f,int t,ll d) { to[p]=t; dis[p]=d; nxt[p]=head[f]; head[f]=p++; } // End int n,m,s,t; // Ford-Fulkerson bool vis[N]; ll dfs(int u,ll flow)//u是當前節點 , flow是送過來的水量 { if(u==t)// End,水送到終點了 return flow; vis[u]=true; for(int i=head[u];i!=-1;i=nxt[i]) { ll c;//存 送水下一個節點能通到終點的最大流量 if(dis[i]>0 //若是水流還能繼續流下去 && !vis[to[i]] //而且要去的點沒有其餘的水流去過 && (c=dfs(to[i],min(flow,dis[i])))!=-1//根據木桶效應,能傳給下一個節點的水量取決於當前節點有的水量與管道(路徑)可以輸送的水量的最小值 //要保證這條路是通的咱們才能夠向他送水,否則就是浪費了 ) { dis[i]-=c;//這個管道已經被佔用一部分用來送水了,須要減掉 dis[i^1]+=c;//給他的反向邊加上相同的水量,送後悔藥 //至於爲何是這樣取出反向邊,下面有講 return c; } } return -1; } // End int main() { ios::sync_with_stdio(true); memset(head,-1,sizeof(head));// init cin>>n>>m>>s>>t; for(int i=1;i<=m;i++) { int u,v,w;cin>>u>>v>>w; add_edge(u,v,w); add_edge(v,u,0);//創建一條暫時沒法通水的反向邊(後面正向邊送水後,須要加上相同的水量) //第一條邊 編號是 0 ,其反向邊爲 1, 衆所周知的 奇數^1=奇數-1, 偶數^1=偶數+1 ,利用這種性質 ,咱們就能夠很快求出反向邊,或者反向邊得出正向邊(這裏說的正反只是相對) } //Ford-Fulkerson ll ans = 0; ll c; // 假設咱們的水無限多 while((c=dfs(s,INF)) != -1) //把源點還能送出去的水所有都送出去,直到送不到終點 { memset(vis,0,sizeof(vis)); //從新開始送沒送出去的水 ans+=c;//記錄總的水量 } cout<<ans<<endl; return 0; }
能夠看出效率比較低,我這裏開了O2也過不了模板題。
上面FF算法太慢了,緣由是由於FF算法太死腦筋了,非要等如今節點水灌滿了,纔會灌其餘的(明明有一個更大的水管不灌)。另外他有時候還很是謙讓,等到明明走到了,卻要返回去等別人水灌好,再灌本身的。
其實,EK算法即是FF算法的BFS版本。複雜度爲\(O(v \times e^2)\)(複雜度這麼高行得通嗎,固然能夠,事實上通常狀況下根本達不到這麼高的上限)。
那我就直接上代碼了。
#include <iostream> #include <cstring> #include <queue> using namespace std; #define INF 0x3f3f3f3f3f3f3f3f typedef long long ll; // Base const int N= 256; const int M= 8192*2; // End // Graph int head[N],nxt[M],to[M]; ll dis[M]; int p; inline void add_edge(int f,int t,ll d) { to[p]=t; dis[p]=d; nxt[p]=head[f]; head[f]=p++; } // End int n,m,s,t; // Edmond-Karp int last[N]; ll flow[N];//記錄當前的點是哪條邊通到來的,這樣多餘的水又能夠這樣送回去. inline bool bfs() //水還能送到終點返回true,反之false { memset(last,-1,sizeof last); queue <int > Q; Q.push(s); flow[s] = INF; //把起點的水量裝填到無限大 while(!Q.empty()) { int k=Q.front(); Q.pop(); if(k==t)// End,水送到終點了 break; for(int i=head[k];i!=-1;i=nxt[i]) { if(dis[i]>0 //若是水流還能繼續流下去 && last[to[i]]==-1 //而且要去的點沒有其餘的水流去過,因此last[to[i]]仍是-1 ){ last[to[i]]=i; // 到 to[i]點 須要走 i這條邊 flow[to[i]]=min(flow[k],dis[i]);//根據木桶效應,能傳給下一個節點的水量取決於當前節點有的水量與管道(路徑)可以輸送的水量的最小值 Q.push(to[i]); //入隊 } } } return last[t]!=-1;//可以送到終點 } // End int main() { ios::sync_with_stdio(true); memset(head,-1,sizeof(head));// init cin>>n>>m>>s>>t; for(int i=1;i<=m;i++) { int u,v,w;cin>>u>>v>>w; add_edge(u,v,w); add_edge(v,u,0);//創建一條暫時沒法通水的反向邊(後面正向邊送水後,須要加上相同的水量) //第一條邊 編號是 0 ,其反向邊爲 1, 衆所周知的 奇數^1=奇數-1, 偶數^1=偶數+1 ,利用這種性質 ,咱們就能夠很快求出反向邊,或者反向邊得出正向邊(這裏說的正反只是相對) } // Edmond-Karp ll maxflow=0; while(bfs())//把源點還能送出去的水所有都送出去,直到送不到終點 { maxflow+=flow[t]; for(int i=t;i!=s;i=to[last[i]^1])//還有多餘的水殘留在管道里,怪惋惜的,原路送回去. { dis[last[i]]-=flow[t]; //這個管道已經被佔用一部分用來送水了,須要減掉 dis[last[i]^1]+=flow[t]; //給他的反向邊加上相同的水量,送後悔藥 //至於爲何是這樣取出反向邊,上面有講 } } cout<<maxflow<<endl; // return 0; }
因而咱們AC了這題。
FF和EK算法都有個比較嚴重的問題.他們每次都只能找到一條增廣路(到終點沒有被堵上的路).Dinic算法不只用到了DFS,還用的了BFS.可是他們發揮的做用是不同的。
種類 | 做用 |
---|---|
DFS | 尋找路 |
BFS | 分層( |
Dinic快就快在能夠多路增廣(兵分三路把你幹掉),這樣咱們能夠節省不少走重複路徑的時間.當找到一條增廣路後,DFS會嘗試用剩餘的流量向其餘地方擴展.找到新的增廣路。
就這???
固然不止,Dinic還有當前弧優化(前面也有哦),總之就是放棄被榨乾的路。
這樣的一通操做以後,複雜度來到了\(O(v^2 \times e)\)
#include <iostream> #include <cstring> #include <queue> using namespace std; #define INF 0x3f3f3f3f3f3f3f3f typedef long long ll; // Base const int N = 256; const int M = 8192 * 2; // End // Graph int head[N], nxt[M], to[M]; ll dis[M]; int p; inline void add_edge(int f, int t, ll d) { to[p] = t; dis[p] = d; nxt[p] = head[f]; head[f] = p++; } // End int n, m, s, t; //Dinic int level[N], cur[N]; //level是各點到起點的深度,cur爲當前弧優化的增廣起點 inline bool bfs() //分層函數,其實就是個普通廣度優先搜索,沒什麼好說的,做用是計算邊權爲1的圖,圖上各點到源點的距離 { memset(level, -1, sizeof(level)); level[s] = 0; memcpy(cur, head, sizeof(head)); cur[s]=head[s]; queue<int> Q; Q.push(s); while (!Q.empty()) { int k = Q.front(); Q.pop(); for (int i = head[k]; i != -1; i = nxt[i]) { //還可以通水的管道纔有價值 if (dis[i] > 0 && level[to[i]] == -1) { level[to[i]] = level[k] + 1; Q.push(to[i]); if(to[i]==t) return true; } } } return false; } ll dfs(int u, ll flow) { if (u == t) return flow; ll flow_now = flow; // 剩餘的流量 for (int i = cur[u]; i != -1 && flow_now > 0; i = nxt[i]) { cur[u] = i; //當前弧優化 //若是水流還能繼續流下去 而且 是向更深處走的 if (dis[i] > 0 && level[to[i]] == level[u] + 1) { ll c = dfs(to[i], min(dis[i], flow_now)); if(!c) level[to[i]]=-1; //剪枝,去除增廣完畢的點 flow_now -= c; //剩餘的水流被用了c dis[i] -= c; //這個管道已經被佔用一部分用來送水了,須要減掉 dis[i ^ 1] += c; //給他的反向邊加上相同的水量,送後悔藥 //至於爲何是這樣取出反向邊,下面有講 } } return flow - flow_now; //返回用掉的水流 } //End int main() { ios::sync_with_stdio(true); memset(head, -1, sizeof(head)); // init cin >> n >> m >> s >> t; for (int i = 1; i <= m; i++) { int u, v, w; cin >> u >> v >> w; add_edge(u, v, w); add_edge(v, u, 0); //創建一條暫時沒法通水的反向邊(後面正向邊送水後,須要加上相同的水量) //第一條邊 編號是 0 ,其反向邊爲 1, 衆所周知的 奇數^1=奇數-1, 偶數^1=偶數+1 ,利用這種性質 ,咱們就能夠很快求出反向邊,或者反向邊得出正向邊(這裏說的正反只是相對) } //Dinic ll ans = 0; while (bfs()) ans += dfs(s, INF); cout << ans << endl; return 0; }
這個算法若是應用在二分圖裏,複雜度爲\(O(v \times sqrt(e))\)
1.《算法競賽進階指南》做者:李煜東
2.《[算法學習筆記(28): 網絡流](算法學習筆記(28): 網絡流 - 知乎 (zhihu.com))》 做者:Pecco
3.《[Dinic當前弧優化 模板及教程](Dinic當前弧優化 模板及教程 - Floatiy - 博客園 (cnblogs.com))》做者:Floatiy