//其實主要仍是本身複習用算法
//假定讀者可以熟練打dinic的板子數組
有上下界的網絡流的核心是」調整」,咱們經過一個初始的未必可行的流調整出一個可行流,還能夠從可行的未必最大/最小的流調整出最大/最小流.網絡
另外一個經常使用技巧是有源匯的流和無源匯的流(循環流)的轉換.除了無源匯可行流的求解,其餘有源匯的上下界網絡流都要用到這個技巧.spa
模型:一個網絡,求出一個流,使得每條邊的流量必須>=Li且<=Hi,每一個點必須知足總流入量=總流出量(流量守恆)(這個流的特色是循環往復,無始無終).插件
這個算法是有上下界網絡流算法的基礎,只要深入理解這個算法其餘算法也就水到渠成,所以我用大篇幅力圖將這個算法的思想和細節闡述清楚.code
可行流算法的核心是將一個不知足流量守恆的初始流調整成知足流量守恆的流.blog
流量守恆,即每一個點的總流入量=總流出量string
若是存在一個可行流,那麼必定知足每條邊的流量都大於等於流量的下限.所以咱們能夠令每條邊的流量等於流量下限,獲得一個初始流,而後建出這個流的殘量網絡.(即:每條邊的流量等於這條邊的流量上限與流量下限之差)這個初始流不必定知足流量守恆,所以最終的可行流必定是在這個初始流的基礎上增大了一些邊的流量使得全部點知足流量守恆.it
所以咱們考慮在殘量網絡上求出一個另不知足流量守恆的附加流,使得這個附加流和咱們的初始流合併以後知足流量守恆.即:io
若是某個點在全部邊流量等於下界的初始流中知足流量守恆,那麼這個點在附加流中也知足流量守恆,
若是某個點在初始流中的流入量比流出量多x,那麼這個點在附加流中的流出量比流入量多x.
若是某個點在初始流中的流入量比流出量少x,那麼這個點在附加流中的流出量比流入量少x.
能夠認爲附加流中一條從u到v的邊上的一個流量表明將原圖中u到v的流量增大1
X的數值能夠枚舉x的全部連邊求出.比較方便的寫法是開一個數組A[],A[i]表示i在初始流中的流入量-流出量的值,那麼A[i]的正負表示流入量和流出量的大小關係,下面就用A[i]表示初始流中i的流入量-流出量
可是dinic算法可以求的是知足流量守恆的有源匯最大流,不能在原網絡上直接求一個這樣的無源匯且不知足流量守恆的附加流.注意到附加流是在原網絡上不知足流量守恆的,這啓發咱們添加一些原網絡以外的邊和點,用這些邊和點實現」原網絡上流量不守恆」的限制.
具體地,若是一個點i在原網絡上的附加流中須要知足流入量>流出量(初始流中流入量<流出量,A[i]<0),那麼咱們須要給多的流入量找一個去處,所以咱們建一條從i出發流量=-A[i]的邊.若是A[i]>0,也就是咱們須要讓附加流中的流出量>流入量,咱們須要讓多的流出量有一個來路,所以咱們建一條指向i的流量=A[i]的邊.
固然,咱們所新建的從i出發的邊也要有個去處,指向i的邊也要有個來路,所以咱們新建一個虛擬源點ss和一個虛擬匯點tt(雙寫字母是爲了和有源匯網絡流中的源點s匯點t相區分).新建的指向i的邊都從ss出發,從i出發的邊都指向tt.一個點要麼有一條邊指向tt,要麼有一條邊來自ss,
指向tt的邊的總流量上限必定等於ss流出的邊的總流量上限,由於每一條邊對兩個點的A[i]貢獻一正一負大小相等,因此所有點的A[i]之和等於0,即小於0的A[i]之和的絕對值=大於0的A[i]之和的絕對值.
若是咱們能找到一個流知足新加的邊都滿流,這個流在原圖上的部分就是咱們須要的附加流(根據咱們的建圖方式,「新加的邊都滿流」和」附加流合併上初始流獲得流量平衡的流」是等價的約束條件).
那麼怎樣找出一個新加的邊都滿流的流呢?能夠發現假如存在這樣的方案,這樣的流必定是咱們所建出的圖的ss-tt最大流,因此跑ss到tt的最大流便可.若是最大流的大小等於ss出發的全部邊的流量上限之和(此時指向tt的邊也必定滿流,由於這兩部分邊的流量上限之和相等).
最後,每條邊在可行流中的流量=容量下界+附加流中它的流量(即跑完dinic以後所加反向邊的權值).
代碼(ZOJ2314 Reactor Cooling)
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=300,maxm=100000; struct edge{ int to,next,w,num; }lst[maxm];int len=0,first[maxn],_first[maxn]; void addedge(int a,int b,int w,int num){ lst[len].num=num; lst[len].to=b;lst[len].next=first[a];lst[len].w=w;first[a]=len++; lst[len].num=num; lst[len].to=a;lst[len].next=first[b];lst[len].w=0;first[b]=len++; } int vis[maxn],dis[maxn],q[maxn],head,tail,s,t,T; bool bfs(){ vis[s]=++T;dis[s]=1;head=tail=0;q[tail++]=s; while(head!=tail){ int x=q[head++]; for(int pt=first[x];pt!=-1;pt=lst[pt].next){ if(lst[pt].w&&vis[lst[pt].to]!=T){ vis[lst[pt].to]=T;dis[lst[pt].to]=dis[x]+1;q[tail++]=lst[pt].to; } } } if(vis[t]==T)memcpy(_first,first,sizeof(first)); return vis[t]==T; } int dfs(int x,int lim){ if(x==t){ return lim; } int flow=0,a; for(int pt=_first[x];pt!=-1;pt=lst[pt].next){ _first[x]=pt; if(lst[pt].w&&dis[lst[pt].to]==dis[x]+1&&(a=dfs(lst[pt].to,min(lst[pt].w,lim-flow)))){ lst[pt].w-=a;lst[pt^1].w+=a;flow+=a; if(flow==lim)return flow; } } return flow; } int dinic(){ int ans=0,x; while(bfs()) while(x=dfs(s,0x7f7f7f7f))ans+=x; return ans; } int low[maxm],ans[maxm]; int totflow[maxn],n,m; void work(){ memset(totflow,0,sizeof(totflow)); memset(first,-1,sizeof(first));len=0; scanf("%d%d",&n,&m); int u,v,b; s=0;t=n+1; for(int i=1;i<=m;++i){ scanf("%d%d%d%d",&u,&v,&low[i],&b); addedge(u,v,b-low[i],i);totflow[u]-=low[i];totflow[v]+=low[i]; } int sum=0; for(int i=1;i<=n;++i){ if(totflow[i]<0){ addedge(i,t,-totflow[i],0); }else{ sum+=totflow[i]; addedge(s,i,totflow[i],0); } } if(dinic()==sum){ puts("YES"); for(int i=1;i<=n;++i){ for(int pt=first[i];pt!=-1;pt=lst[pt].next){ if(lst[pt].num==0||pt%2==0)continue; ans[lst[pt].num]=lst[pt].w+low[lst[pt].num]; } } for(int i=1;i<=m;++i)printf("%d\n",ans[i]); }else puts("NO"); } int main(){ int tests;scanf("%d",&tests); while(tests--){ work();if(tests)printf("\n"); } return 0; }
2. 有源匯有上下界可行流
模型:如今的網絡有一個源點s和匯點t,求出一個流使得源點的總流出量等於匯點的總流入量,其餘的點知足流量守恆,並且每條邊的流量知足上界和下界限制.
源點和匯點不知足流量守恆,這讓咱們很難辦,所以咱們想辦法把問題轉化成容易處理的每一個點都知足流量守恆的無源匯狀況.
爲了使源匯點知足流量守恆,咱們須要有邊流入源點s,有邊流出匯點t.注意到源點s的流出量等於匯點t的流入量,咱們就能夠從匯點t向源點s連一條下界爲0上界爲無窮大的邊,至關於把從源點s流出的流量再流回來.在這樣的圖中套用上面的算法求出一個可行的循環流,拆掉從匯點t到源點s的邊就獲得一個可行的有源匯流.
這裏有一個小問題:最後獲得的可行的有源匯流的流量是多少?
能夠發現,循環流中必定知足s流出的總流量=流入s的總流量,假定原圖中沒有邊流入s,那麼s流出的流量就是t到s的無窮邊的流量,也就是s-t可行流的流量.所以咱們最後看一下t到s的無窮邊的流量(即dinic跑完以後反向邊的權值)便可知道原圖中有源匯可行流的流量.
代碼:這個可行流算法在有源匯有上下界最大流/最小流中都會用到,能夠看下面兩個算法的代碼
3.有源匯有上下界最大流
模型:如今的網絡有一個源點s和匯點t,求出一個流使得源點的總流出量等於匯點的總流入量,其餘的點知足流量守恆,並且每條邊的流量知足上界和下界限制.在這些前提下要求總流量最大.
首先套用上面的算法求出一個有源匯有上下界可行流.此時的流不必定最大.
接下來在殘量網絡上跑s-t最大流便可.
最終的最大流流量=可行流流量(即t到s的無窮邊上跑出的流量)+新增廣出的s-t流量
問題:會不會增廣的時候使得一些邊不知足流量下限?
不會.由於咱們一開始建的圖就是把大小等於流量下限的流量拿出去以後的殘量網絡,這些流量根本沒有在圖中出現.
代碼:ZOJ 3229 Shoot The Bullet 東方文花帖 (因爲ZOJ的評測插件彷佛掛了,並不知道對不對,請謹慎取用)
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=2005,maxm=100005; const int inf=0x7f7f7f7f; struct edge{ int to,next,w,num; }lst[maxm];int len=0,first[maxn],_first[maxn]; void addedge(int a,int b,int w,int num){ lst[len].num=num; lst[len].to=b;lst[len].next=first[a];lst[len].w=w;first[a]=len++; lst[len].num=num; lst[len].to=a;lst[len].next=first[b];lst[len].w=0;first[b]=len++; } int q[maxn],vis[maxn],dis[maxn],T,s,t,head,tail,ss,tt; bool bfs(){ head=tail=0;vis[s]=++T;q[tail++]=s; while(head!=tail){ int x=q[head++]; for(int pt=first[x];pt!=-1;pt=lst[pt].next){ if(lst[pt].w&&vis[lst[pt].to]!=T){ vis[lst[pt].to]=T;dis[lst[pt].to]=dis[x]+1;q[tail++]=lst[pt].to; } } } if(vis[t]==T)memcpy(_first,first,sizeof(first)); return vis[t]==T; } int dfs(int x,int lim){ if(x==t)return lim; int flow=0,a; for(int pt=_first[x];pt!=-1;pt=lst[pt].next){ _first[x]=pt; if(lst[pt].w&&dis[lst[pt].to]==dis[x]+1&&(a=dfs(lst[pt].to,min(lst[pt].w,lim-flow)))){ lst[pt].w-=a;lst[pt^1].w+=a;flow+=a; if(flow==lim)return flow; } } return flow; } int dinic(){ int ans=0,x; while(bfs()) while(x=dfs(s,inf))ans+=x; return ans; } int totflow[maxn]; void Add(int a,int b,int lo,int hi,int num){ totflow[a]-=lo;totflow[b]+=lo; addedge(a,b,hi-lo,num); } int low[maxm],ans[maxm]; int n,m,tot; void bound_flow(){ int sum=0; for(int i=s;i<=t;++i){ if(totflow[i]<0){ addedge(i,tt,-totflow[i],0); }else{ sum+=totflow[i]; addedge(ss,i,totflow[i],0); } } addedge(t,s,0x7f7f7f7f,0); int tmps=s,tmpt=t; s=ss;t=tt; if(dinic()==sum){ for(int pt=first[ss];pt!=-1;pt=lst[pt].next){ lst[pt].w=lst[pt^1].w=0; } for(int pt=first[tt];pt!=-1;pt=lst[pt].next){ lst[pt].w=lst[pt^1].w=0; } int flow0=lst[len-1].w; lst[len-1].w=lst[len-2].w=0; s=tmps;t=tmpt; printf("%d\n",flow0+dinic()); for(int i=1;i<=m;++i){ for(int pt=first[i+n];pt!=-1;pt=lst[pt].next){ if(lst[pt].num!=0){ ans[lst[pt].num]=lst[pt].w+low[lst[pt].num]; } } } for(int i=1;i<=tot;++i)printf("%d\n",ans[i]); }else{ printf("-1\n"); } } void work(){ s=0;t=n+m+1; ss=n+m+2;tt=n+m+3; memset(first,-1,sizeof(first));len=0; memset(totflow,0,sizeof(totflow)); int x,y; for(int i=1;i<=m;++i){ scanf("%d",&x); Add(n+i,t,x,inf,0); } int l,h; tot=0; for(int i=1;i<=n;++i){ scanf("%d%d",&x,&y); Add(s,i,0,y,0); for(int j=1;j<=x;++j){ ++tot; scanf("%d%d%d",&y,&l,&h); Add(i,n+y+1,l,h,tot);low[tot]=l; } } bound_flow();printf("\n"); } int main(){ while(scanf("%d%d",&n,&m)!=EOF)work(); return 0; }
4.有源匯有上下界最小流
模型:如今的網絡有一個源點s和匯點t,求出一個流使得源點的總流出量等於匯點的總流入量,其餘的點知足流量守恆,並且每條邊的流量知足上界和下界限制.在這些前提下要求總流量最小.
依然是先跑出一個有源匯可行流.這時候的流也不必定是最小的.假如咱們能在殘量網絡上找到一條s-t的路徑使得去掉這條路徑上的流量以後仍然知足流量下限,咱們就能夠獲得一個更小的流.好像咱們並無什麼算法能夠」找到儘量多的可以去除流量的路徑」
這時候須要咱們再理解一下dinic的反向邊.反向邊的流量增長等價於正向邊的的流量減小.所以咱們在殘量網絡上找出t到s的流就至關於減少了s到t的流,所以咱們在跑出可行流的殘量網絡上跑t-s最大流,用可行流的大小減去這一次t-s最大流的大小就是最小流的大小.(t-s最大流實際上是儘可能縮減s-t方向的流).
問題:會不會使流量縮減到不知足流量下限?
不會.和有源匯有上下限的最大流同樣,咱們以前從每條邊上拿出了大小等於流量下限的流量構成初始流,這些流量不在咱們建出的圖中.最極端的狀況是縮減到全部邊的流量等於流量下限,不會更小了.
代碼:bzoj2502 清理雪道
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=205,maxm=100005; struct edge{ int to,next,w; }lst[maxm];int len=0,first[maxn],_first[maxn]; void addedge(int a,int b,int w){//printf("Add %d %d\n",a,b); lst[len].to=b;lst[len].next=first[a];lst[len].w=w;first[a]=len++; lst[len].to=a;lst[len].next=first[b];lst[len].w=0;first[b]=len++; } int q[maxn],vis[maxn],dis[maxn],head,tail,s,t,T,ss,tt; bool bfs(){ head=tail=0;vis[s]=++T;dis[s]=1;q[tail++]=s; while(head!=tail){ int x=q[head++]; for(int pt=first[x];pt!=-1;pt=lst[pt].next){ if(lst[pt].w&&vis[lst[pt].to]!=T){ vis[lst[pt].to]=T;dis[lst[pt].to]=dis[x]+1;q[tail++]=lst[pt].to; } } } if(vis[t]==T)memcpy(_first,first,sizeof(first)); return vis[t]==T; } int dfs(int x,int lim){ if(x==t)return lim; int flow=0,a; for(int pt=_first[x];pt!=-1;pt=lst[pt].next){ _first[x]=pt; if(lst[pt].w&&dis[lst[pt].to]==dis[x]+1&&(a=dfs(lst[pt].to,min(lst[pt].w,lim-flow)))){ lst[pt].w-=a;lst[pt^1].w+=a;flow+=a; if(flow==lim)return flow; } } return flow; } int dinic(){ int ans=0,x; while(bfs()){ while(x=dfs(s,0x7f7f7f7f))ans+=x; } return ans; } int totflow[maxn]; void del(int x){ for(int pt=first[x];pt!=-1;pt=lst[pt].next)lst[pt].w=lst[pt^1].w=0; } int main(){ int n;scanf("%d",&n); int x,y; memset(first,-1,sizeof(first)); for(int i=1;i<=n;++i){ scanf("%d",&x); for(int j=1;j<=x;++j){ scanf("%d",&y); totflow[i]--;totflow[y]++; addedge(i,y,0x7f7f7f7f); } } s=0;t=n+1;ss=n+2,tt=n+3; for(int i=1;i<=n;++i){ addedge(s,i,0x7f7f7f7f); addedge(i,t,0x7f7f7f7f); } for(int i=1;i<=n;++i){ if(totflow[i]<0){ addedge(i,tt,-totflow[i]); }else{ addedge(ss,i,totflow[i]); } } addedge(t,s,0x7f7f7f7f); int tmps=s,tmpt=t; s=ss;t=tt; dinic(); int flow0=lst[len-1].w; lst[len-1].w=lst[len-2].w=0; del(ss);del(tt); s=tmpt;t=tmps; printf("%d\n",flow0-dinic()); return 0; }
5.有源匯有上下界費用流(待填坑,bzoj3876和Codeforces 708D,不過這兩道題均可以用費用流的技巧避開上下界網絡流)