幾句廢話:
讀了劉汝佳的書之后,感覺一切都是那么茫然,於是自己在網上找教程,自己一點點碼的,大概用了三天。
網絡流基礎:
看來我很有必要說一下網絡流的基礎
網絡流問題就是給你一個圖,每個圖的邊權叫做這條邊的流量,問你從起始點出發,有多少值能通過這些邊流到重點
我知道你沒看懂,舉個例子:
如圖:
最大值為
從1到2到4運6個
從1到2到3到4運1個
從1到3到4運3個
一共運10個。
舉例說完了,那么我說幾個定義:
容量,就只一條邊的權值,表示能從這條邊運送的最大值
流量,表示一條邊實際上流過的最大值
那么,說算法的時間到了,還是先上數據
(數據為上圖所示)
4 5//四個點,五條邊 1 2 8 1 3 3 2 3 1 2 4 6 3 4 5
開始,我們假設所有邊的流量都是0
以這個數據,這樣可以達到。
於是我們試圖增加一些變得流量,使得重點的流量更大。
那么如何增加?就需要我們找増廣路。
什么是増廣路,就是一條從起點,到終點的一條每條邊容量-實際流量>的路0。
比如
0/8表示一條邊流量為0,容量為8
現在很明顯可以看出,存在一條増廣路1——2——4
然后怎么辦,把這條路上的每條邊流量都加上每條邊容量與流量差的最小值
例如8-0>6-0 因此1——2、2——4這兩條邊的流量都加上6,並且答案也加上6
然后我們繼續找増廣路,又發現一條1——2——3——4
還是按剛才的,找出最小值,為1,所以這條增光路上每一邊流量加1
然后ans再加一變成7
然而我們繼續找増廣路,發現1——3——4
按照剛才,流量加上2,變成
ans加上3變成10
然后發現沒有増廣路了,於是算法結束,答案是10.
等等,這就要上代碼了?傳說中得noi算法網絡流就這么簡單?不存在的
細心的同學(滑稽)會發現,這種情況就很神奇
你可能回算1——2——3——4這條増廣路,於是答案是1,但是事實證明最優答案明顯是2,那么問題出在哪里?
因為我們沒有給予返回的機會,也就是相當於第一次找到的不是最優解,那么怎么辦?
所以,我們要有一個反向邊,來給程序反悔的機會,每條邊都創建一條反向邊,反向邊的初始容量是0,流量都為負數。
假設1——2這條邊本來權值是1,流量也是1,那么他的反向邊的容量是0,流量是-1,這個應該好理解。
上圖就會變為下圖:
然后以剛才舉例,當確定増廣路1——2——3——4后,圖是這樣的:
於是,我們又找到了一條新的増廣路:1——3——2——4
(因為0-(-1)=1,所以這條邊也可以走)
那么答案是1+1=2
那么問題來了,為什么這樣就算一種呢?
因為制造相反邊,如2——3,就是相當於吧原來從而流到3的量流回來了。
這樣就可以求出最大值。
口胡內容就到這里,其實我寫的不是很清楚,大家看劉汝佳的可能會更清楚一些,但是重點來了!!!
這道題代碼實現非常之困難,至少對於我來說,所以劉汝佳的代碼我壓根沒看懂。
因此,我自己寫得一份淺顯易懂的代碼
沒有vector!!!沒有指針!!!
而且有復雜的注釋!!!
#include <iostream> #include <cmath> #include <cstdio> #include <algorithm> #include <cstdlib> #include <cstring> #include <queue> using namespace std; int n,m;//n個點,m條邊 int head[100010];//這是鄰接表的標志,head[i]表示以i為頂點的第一條邊 struct edge { int cap,flow;//cap為容量,flow為流量 int from,to;//一條邊的起點終點 int next;//和這條邊起點相同的下一條邊(鄰接表標志) }map[100010]; int index=-1;//鄰接表輸入數組 int flag=0;//退出的標記 void build_edge(int a,int b,int c)//構造鄰接表,插入連接表 { index++; map[index].from=a; map[index].to=b; map[index].cap=c; map[index].flow=0; map[index].next=head[a]; head[a]=index; index++; map[index].from=b;//插入相反邊 map[index].to=a; map[index].cap=0; map[index].flow=0; map[index].next=head[b]; head[b]=index; return ; } int bfs()//運用bfs找到増廣路 { int min_flow[100010];//min_flow[i]代表到第i好點時,當前所走過變的容量-流量的最小值 memset(min_flow,0,sizeof(min_flow)); queue<int> Q;//用隊列 維護 min_flow[1]=999999;//開始為無限大 int p[100010];//這個很重要,構造增光路時,記錄當前點是由那條邊找到的,以此找到増廣路時能從終點以此到起點的邊的流量加上最小值 p[1]=-1; Q.push(1);//起點入隊 while(!Q.empty()) { int now=Q.front(); Q.pop(); for(int e=head[now];e!=-1;e=map[e].next)//鄰接表枚舉當前點的所有連邊 { int v=map[e].cap-map[e].flow;//v就是為當前邊容量-流量 if(v>0 && !min_flow[map[e].to])//當v>0 並且這個點沒有訪問過時 { p[map[e].to]=e;//記錄到達這個點的邊的序號 min_flow[map[e].to]=min(min_flow[now],v);//維護最小值 Q.push(map[e].to);//維護bfs } } if(min_flow[n]!=0)//如果終點得到更新,說明找到増廣路,直接break break; } if(min_flow[n]==0)//當沒有更新到終點,也就是重點最小增加值為0時,說明沒有増廣路了,直接完事 flag=1; for(int e=n;;e=map[p[e]].from)//從終點開始,以此倒着走増廣路,把沿途上邊的流量都加上終點最小更新值 { if(p[e]==-1)//如果到頭就完事 break; map[p[e]].flow+=min_flow[n];//流量加上最小更新至 map[p[e]^1].flow-=min_flow[n];//反向邊減去 } return min_flow[n];//返回最小更新至,加到答案當中 } int max_flow() { int flow=0;//這就是神聖的答案 while(1) { flow+=bfs();//循環搜索 if(flag==1)//沒有増廣路,就直接跳出 break; } return flow;//返回答案 } int main() { cin>>n>>m; for(int i=0;i<=m;i++)//初始化,很重要,制勝之點 { map[i].next=-1; head[i]=-1; } for(int i=1;i<=m;i++)//讀入,構造鄰接表 { int a,b,c,d; cin>>a>>b>>c; build_edge(a,b,c); } cout<<max_flow();//輸出答案 } /* 測試數據 4 4 1 2 2 2 4 2 1 3 3 3 4 1 */
學信息不易,作業還沒動,求關注!!!