筆者在寫做這篇筆記以前作了整整兩天的最大流,而後。。。發現網絡流24題裏有不少怎麼看都是不可作的題目,因而solution了一把,發現要去切一下費用流這個東東,因而借鑑各類blog和題解,如今勉強搞懂了這個東西,因此做一篇筆記聊以記錄和往後複習。ios
若是您尚未學習網絡流的基本概念,請出門左轉百度吧。。(事實是筆者太懶只整理了題目而沒有詳解)。算法
首先,先明白費用流是什麼:編程
費用流創建在網絡最大流的基礎上,一張圖中最大流有且僅有一個,可是最大流條數每每不止一條,這時候對於咱們來講,可能要找出這些最大流中最小(或者最大)的那一條路徑,這就是最小(最大)費用最大流 。網絡
實現的基本思想:給出一張網絡,那麼這個網絡的最大流必定是個定值(即便它有多種方法實現這個最大值),咱們只要從當前可行流開始增廣的時候,選擇費用最少的一條路徑就能夠了。ide
咱們有多種方法實現最小費用的計算:學習
其一:最原始的E-K算法+spfa。優化
這個天然沒什麼說的(若是您會了最大流殊不知道spfa,那我也只能說您666),將弧的費用看作是路徑長度,便可轉化爲求最短路的問題了。只須要所走的最短路知足兩個條件便可:一個是殘量不爲0,一個是最短路的那個條件。建圖的方法依題來建,不過大致建出來和DINIC差很少。spa
以洛谷P3381的模板題爲例:.net
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #define ll long long 8 #define inf 50000000 9 #define re register 10 using namespace std; 11 struct po 12 { 13 int from,to,dis,nxt,w; 14 }edge[250001]; 15 int head[250001],cur[1000001],dep[60001],n,m,s,t,u,num=-1,x,y,l,tot,sum,k,fa[10001]; 16 int dis[5001],b[5001],xb[5001],flow[5001]; 17 inline int read() 18 { 19 int x=0,c=1; 20 char ch=' '; 21 while((ch>'9'||ch<'0')&&ch!='-')ch=getchar(); 22 while(ch=='-')c*=-1,ch=getchar(); 23 while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar(); 24 return x*c; 25 } 26 inline void add_edge(int from,int to,int w,int dis) 27 { 28 edge[++num].nxt=head[from]; 29 edge[num].from=from; 30 edge[num].to=to; 31 edge[num].w=w; 32 edge[num].dis=dis; 33 head[from]=num; 34 } 35 inline void add(int from,int to,int w,int dis) 36 { 37 add_edge(from,to,w,dis); 38 add_edge(to,from,0,-dis); 39 } 40 inline bool bfs() 41 { 42 memset(dis,100,sizeof(dis)); 43 memset(b,0,sizeof(b)); 44 queue<int> q; 45 while(!q.empty()) 46 q.pop(); 47 for(re int i=1;i<=n;i++) 48 { 49 fa[i]=-1; 50 } 51 b[s]=1;dis[s]=0;fa[s]=0; 52 flow[s]=inf;q.push(s); 53 while(!q.empty()) 54 { 55 int u=q.front(); 56 q.pop(); 57 b[u]=0; 58 for(re int i=head[u];i!=-1;i=edge[i].nxt) 59 { 60 int v=edge[i].to; 61 if(edge[i].w>0&&dis[v]>dis[u]+edge[i].dis) 62 { 63 dis[v]=dis[u]+edge[i].dis; 64 fa[v]=u; 65 xb[v]=i; 66 flow[v]=min(flow[u],edge[i].w); 67 if(!b[v]){b[v]=1,q.push(v);} 68 } 69 } 70 } 71 return dis[t]<inf; 72 } 73 inline void max_flow() 74 { 75 while(bfs()) 76 { 77 int k=t; 78 while(k!=s) 79 { 80 edge[xb[k]].w-=flow[t]; 81 edge[xb[k]^1].w+=flow[t]; 82 k=fa[k]; 83 } 84 tot+=flow[t]; 85 sum+=flow[t]*dis[t]; 86 } 87 } 88 int main() 89 { 90 memset(head,-1,sizeof(head)); 91 n=read();m=read();s=read();t=read(); 92 for(re int i=1;i<=m;i++) 93 { 94 x=read();y=read();l=read(); 95 int d=read(); 96 add(x,y,l,d); 97 } 98 max_flow(); 99 cout<<tot<<" "<<sum; 100 }
能夠看出,雖然筆者的常數優化比較優秀,然而仍是卡着時間跑過最後兩個點。code
其二:zkw費用流
這個方法筆者沒有深究,仔細閱讀以後發現要使zkw費用流算法達到最好的效率,那麼必須使用KM算法。然而不會KM算法怎麼辦,您能夠跳過這個部分,直接看第三種實現方式。
原始的EK算法雖然在作題的時候不會出很大的問題——由於如今的費用流的題目數據都不是很大。它的缺點比較明顯:在增廣的時候單路增廣,致使速度減慢。zkw大神因而乎發明了一種能夠直接多路增廣的算法,並用KM算法節省了spfa或者迪傑斯特拉的時間,然而悲催的是,筆者本身也對KM算法不是很理解,抱歉沒法寫出程序比較一下時空複雜度。不過爲了保持博文的完整性,仍是放上方法發明者zkw的欽定標程:
#include <cstdio> #include <cstring> using namespace std; const int maxint=~0U>>1; int n,m,pi1,cost=0; bool v[550]; struct etype { int t,c,u; etype *next,*pair; etype(){} etype(int t_,int c_,int u_,etype* next_): t(t_),c(c_),u(u_),next(next_){} void* operator new(unsigned,void* p){return p;} } *e[550]; int aug(int no,int m) { if(no==n)return cost+=pi1*m,m; v[no]=true; int l=m; for(etype *i=e[no];i;i=i->next) if(i->u && !i->c && !v[i->t]) { int d=aug(i->t,l<i->u?l:i->u); i->u-=d,i->pair->u+=d,l-=d; if(!l)return m; } return m-l; } bool modlabel() { int d=maxint; for(int i=1;i<=n;++i)if(v[i]) for(etype *j=e[i];j;j=j->next) if(j->u && !v[j->t] && j->c<d)d=j->c; if(d==maxint)return false; for(int i=1;i<=n;++i)if(v[i]) for(etype *j=e[i];j;j=j->next) j->c-=d,j->pair->c+=d; pi1 += d; return true; } int main() { freopen("costflow.in","r",stdin); freopen("costflow.out","w",stdout); scanf("%d %d",&n,&m); etype *Pe=new etype[m+m]; while(m--) { int s,t,c,u; scanf("%d%d%d%d",&s,&t,&u,&c); e[s]=new(Pe++)etype(t, c,u,e[s]); e[t]=new(Pe++)etype(s,-c,0,e[t]); e[s]->pair=e[t]; e[t]->pair=e[s]; } do do memset(v,0,sizeof(v)); while(aug(1,maxint)); while(modlabel()); printf("%d\n",cost); return 0; }
有興趣深究的朋友能夠從博文最後訪問zkw大神的博客。
其三:原始對偶算法
講這個算法以前先說一下zkw費用流的一些不適用性,zkw大神原話:在某一些圖上, 算法速度很是快, 另外一些圖上卻比純 SPFA 增廣的算法慢. 很多同窗通過實測總結的結果是稠密圖上比較快, 稀疏圖上比較慢。其實也不徹底是由於這樣。咱們比較一下原始的EK和zkw算法,能夠發現原始EK主要慢在spfa一遍一遍的隊列操做和重複訪問節點,以及只能進行單路增廣的限制。而zkw算法只是一個對邊的掃描操做,而且重標號後能夠多路增廣。然而缺點也顯而易見,若是這個圖是出題者別有用心製造的,那麼流量不大, 費用不小, 增廣路還較長,每次添加一條邊,而後嘗試增廣,湊不成最短路,再添重標號,繼續嘗試。形成了大量的時間浪費。
那麼有沒有一種方式結合了這兩種算法的優勢呢?有的,咱們能夠用一種和dinic很類似的思路(至少筆者是這麼認爲)。
費用流的算法大體分爲兩種, 一種是經典的解法, 如消圈, 增廣路, 原始對偶等等, 特色是步步爲營, 維持可行性或者最優性其中之一, 再不斷對另外一方面做出改進. 另外一種就比較現代一些, 典型的例子是鬆弛算法和網絡單純形, 因爲放鬆了對求解過程當中解的限制條件, 使得其速度遠遠超過經典解法, 同時也增長了編程難度和理解障礙. 下面要說的原始對偶算法, 速度天然不可能比鬆弛和網絡單純形快, 但應該是經典解法中的佼佼者了。 ——zkw
咱們在spfa的時候使用SLF優化來維護距離編號,而後利用多路增廣,達到一個比較好的效果。下面放上筆者弱弱的代碼:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #define ll long long #define inf 50000000 #define re register using namespace std; struct po { int from,to,dis,nxt,w; }edge[250001]; int head[250001],cur[1000001],dep[60001],n,m,s,t,u,num=-1,x,y,l,tot,sum,k,fa[10001]; int dis[5001],b[5001],xb[5001],flow[5001]; inline int read() { int x=0,c=1; char ch=' '; while((ch>'9'||ch<'0')&&ch!='-')ch=getchar(); while(ch=='-')c*=-1,ch=getchar(); while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar(); return x*c; } inline void add_edge(int from,int to,int w,int dis) { edge[++num].nxt=head[from]; edge[num].to=to; edge[num].w=w; edge[num].dis=dis; head[from]=num; } inline void add(int from,int to,int w,int dis) { add_edge(from,to,w,dis); add_edge(to,from,0,-dis); } inline bool spfa() { memset(b,0,sizeof(b)); for(re int i=0;i<=n;i++) dis[i]=inf; dis[t]=0;b[t]=1; deque<int> q; q.push_back(t); while(!q.empty()) { int u=q.front(); b[u]=0; q.pop_front(); for(re int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].to; if(edge[i^1].w>0&&dis[v]>dis[u]-edge[i].dis) { dis[v]=dis[u]-edge[i].dis; if(!b[v]) { b[v]=1; if(!q.empty()&&dis[v]<dis[q.front()]) q.push_front(v); else q.push_back(v); } } } } return dis[s]<inf; } inline int dfs(int u,int low) { if(u==t) { b[t]=1; return low; } int diss=0; b[u]=1; for(re int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].to; if(!b[v]&&edge[i].w!=0&&dis[u]-edge[i].dis==dis[v]) { int check=dfs(v,min(edge[i].w,low)); if(check>0) { tot+=check*edge[i].dis; edge[i].w-=check; edge[i^1].w+=check; low-=check; diss+=check; if(low==0) break; } } } return diss; } inline int max_flow() { int ans=0; while(spfa()) { b[t]=1; while(b[t]) { memset(b,0,sizeof(b)); ans+=dfs(s,inf); } } return ans; } int main() { memset(head,-1,sizeof(head)); n=read();m=read();s=read();t=read(); for(re int i=1;i<=m;i++) { x=read();y=read();l=read();int d=read(); add(x,y,l,d); } cout<<max_flow()<<" "; cout<<tot; }
直接快了50%,能夠看出多路增廣的優點仍是顯然的。
然而若是並無看明白,能夠訪問如下這位大神的博客:一種更高效的費用流。
部份內容或有重複衝突請神犇們諒解,多是想到一塊兒去了。