网络流小结
花了一些时间复习了一下网络流的一些相关内容。
http://www.cnblogs.com/LadyLex/p/7601119.html
最大流
$$Maximize \sum f[i]\\s.t. \sum f[i][j]-f[j][i]=0 (i≠S,T)\\ \sum f[i][j]-f[j][i]=v(f) (i=S)\\ \sum f[i][j]-f[j][i]=-v(f) (i=T)$$
很简单的概念,从问题到算法都很简单。即使是优化算法,只要稍微深入思考就能理解。
比较基础的是Ford-Fulkerson算法,优化的有Dinic,ISAP和预流推进算法等。
个人比较常用dinic,ISAP实际上是前者的另一种实现形式,虽然有时速度会加快但实现稍复杂且有的数据可能会被卡。预流推进算法实现较难,考场实用性不高。
dinic实际上就是BFS和最大流的结合吧,注意记得加当前弧优化
重点主要就是理解“流量平衡”“反悔边”的概念,其余都是比较普通的图论问题。
两个优化:
1.若一个点dfs后增广量为0(即无法增广),则将其dis设为-1(等于删除这个点)
2.当前弧优化:若一条边已经增广完毕(容量已为0),下次dfs到这个点的时候就直接从下一条边开始增广。
Dinic:
1 //Dinic(O(V2*E),n<=100000,m<=500000),POJ1149: 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 #define rep(i,l,r) for (int i=l; i<=r; i++) 7 #define For(i,x) for (int i=h[x],k; i; i=e[i].nxt) 8 using namespace std; 9 10 const int N=105,inf=1000000000; 11 vector<int> a[N]; 12 int v,x,y,m,n,S,T,cnt=1,d[N],q[N],h[N],cur[N],pig[1005],L[1005]; 13 struct E{ int to,nxt,v; }e[100005]; 14 15 void add(int u,int v,int w){ 16 e[++cnt]=(E){v,h[u],w}; h[u]=cnt; 17 e[++cnt]=(E){u,h[v],0}; h[v]=cnt; 18 } 19 20 bool bfs(){ 21 memset(d,0,sizeof(d)); q[1]=S; d[S]=1; 22 for (int st=0,ed=1; st!=ed; ){ 23 int x=q[++st]; 24 For(i,x) if (e[i].v && !d[k=e[i].to]) 25 d[k]=d[x]+1,q[++ed]=k; 26 } 27 return d[T]; 28 } 29 30 int dfs(int x,int lim){ 31 if (x==T) return lim; 32 int t,c=0; 33 for (int i=cur[x],k; i; i=e[i].nxt) 34 if (d[k=e[i].to]==d[x]+1){ 35 t=dfs(k,min(lim-c,e[i].v)); 36 e[i].v-=t; e[i^1].v+=t; c+=t; 37 if (!e[i].v) cur[x]=i; 38 if (c==lim) return lim; 39 } 40 if (!c) d[x]=-1; return c; 41 } 42 43 int dinic(){ 44 int ans=0; 45 while (bfs()){ 46 rep(i,0,T) cur[i]=h[i]; 47 ans+=dfs(0,inf); 48 } 49 return ans; 50 } 51 52 int main(){ 53 scanf("%d%d",&m,&n); S=0; T=n+1; 54 rep(i,1,m) scanf("%d",pig+i); 55 rep(i,1,n){ 56 for (scanf("%d",&x); x--; ) scanf("%d",&y),a[i].push_back(y); 57 scanf("%d",&y); add(i,T,y); 58 } 59 rep(i,1,n) 60 rep(j,0,(int)a[i].size()-1){ 61 v=a[i][j]; 62 if(!L[v]) add(0,i,pig[v]); else add(L[v],i,inf); 63 L[v]=i; 64 } 65 printf("%d\n",dinic()); 66 return 0; 67 }
最小费用最大流
NOI2012 美食节 优化:流了边k的流之后再添加边k+1
网络流24题 之 餐巾问题
https://www.cnblogs.com/candy99/p/6127287.html
保证流量最大的情况下最小化$\sum f[e]*w[e] , e \in E$ 。
有普通的Edmonds-Karp和ZKW费用流两种写法。前者实际上就是SPFA,很多时候ZKW会比前者快不少,曾尝试用过ZKW但因为不稳定就放弃了。
EK:
1 //最小费用最大流(MCMF,O(KV*E2),n<=5000,m<=10000) 2 3 #include<cstdio> 4 #include<cstring> 5 #include<algorithm> 6 #define rep(i,l,r) for (int i=l; i<=r; i++) 7 using namespace std; 8 11 const int N=2010,inf=100000000; 12 int n,m,S,T,ans,u,v,c,st,ed,mn,cnt,h[N],pre[N],inq[N*100],dis[N],q[N]; 13 struct E{ int to,c,nxt,f; }e[50100]; 14 15 void add(int u,int v,int w,int f){ 16 e[++cnt]=(E){v,w,h[u],f}; h[u]=cnt; 17 e[++cnt]=(E){u,-w,h[v],0}; h[v]=cnt; 18 } 19 20 int spfa(){ 21 rep(i,0,n+2) pre[i]=-1,inq[i]=0,dis[i]=inf; 22 inq[S]=1; dis[S]=0; st=0; ed=1; q[1]=S; 23 while (st!=ed){ 24 int x=q[++st]; inq[x]=0; 25 for (int i=h[x],k; i; i=e[i].nxt) 26 if (e[i].f && dis[k=e[i].to]>dis[x]+e[i].c){ 27 dis[k]=dis[x]+e[i].c; pre[k]=i; 28 if (!inq[k]) inq[k]=1,q[++ed]=k; 29 } 30 } 31 return dis[T]!=dis[0]; 32 } 33 34 void mcmf(){ 35 for (ans=0; spfa(); ans+=dis[T]*mn){ 36 mn=inf; 37 for (int i=pre[T]; ~i; i=pre[e[i^1].to]) mn=min(mn,e[i].f); 38 for (int i=pre[T]; ~i; i=pre[e[i^1].to]) e[i].f-=mn,e[i^1].f+=mn; 39 } 40 } 41 42 int main(){ 43 freopen("poj2135.in","r",stdin); 44 freopen("poj2135.out","w",stdout); 45 while (scanf("%d%d",&n,&m)==2){ 46 memset(h,0,sizeof(h)); cnt=1; S=n+1; T=n+2; 47 rep(i,1,m) scanf("%d%d%d",&u,&v,&c),add(u,v,c,1),add(v,u,c,1); 48 add(S,1,0,2); add(n,T,0,2); mcmf(); printf("%d\n",ans); 49 } 50 return 0; 51 }
ZKW:
1 //zkw费用流(不比朴素快多少,不稳定) 2 #include<cstdio> 3 #include<cstring> 4 #include<iostream> 5 #include<algorithm> 6 #define rep(i,l,r) for (int i=l; i<=r; i++) 7 #define For(i,x) for (int i=h[x],k; i; i=e[i].nxt) 8 using namespace std; 9 const int N=2010,inf=1000000000; 10 11 template<typename T>void rd(T &x){ 12 int t; char ch; 13 for (t=0; !isdigit(ch=getchar()); t=(ch=='-')); 14 for (x=ch-'0'; isdigit(ch=getchar()); x=x*10+ch-'0'); 15 if (t) x=-x; 16 } 17 18 bool vis[N],inq[N]; 19 int S,T,n,m,cnt=1,ans,u,v,c,t,h[N],dis[N],q[N]; 20 struct E{ int to,nxt,v,c; }e[2000005]; 21 void inc(int &x){ x++; if (x==2001) x=1; } 22 23 void add(int u,int v,int w,int c){ 24 e[++cnt]=(E){v,h[u],w,c}; h[u]=cnt; 25 e[++cnt]=(E){u,h[v],0,-c}; h[v]=cnt; 26 } 27 28 bool bfs(){ 29 rep(i,0,T) dis[i]=inf; 30 memset(inq,0,sizeof(inq)); dis[T]=0; q[1]=T; inq[T]=1; 31 for (int st=0,ed=1; st!=ed; ){ 32 inc(st); int x=q[st]; inq[x]=0; 33 For(i,x) if (e[i^1].v && dis[x]+e[i^1].c<dis[k=e[i].to]){ 34 dis[k]=dis[x]+e[i^1].c; 35 if (!inq[k]) inq[k]=1,inc(ed),q[ed]=k; 36 } 37 } 38 return dis[S]!=inf; 39 } 40 41 int dfs(int x,int lim){ 42 vis[x]=1; if (x==T) return lim; 43 int w,c=0; 44 For(i,x) if (e[i].v && !vis[k=e[i].to] && dis[x]==dis[k]+e[i].c){ 45 w=dfs(k,min(e[i].v,lim-c)); 46 e[i].v-=w; e[i^1].v+=w; ans+=e[i].c*w; c+=w; 47 if (c==lim) return lim; 48 } 49 return c; 50 } 51 52 int zkw(){ 53 int ans=0; 54 while (bfs()){ 55 vis[T]=1; 56 while (vis[T]) memset(vis,0,sizeof(vis)),ans+=dfs(S,inf); 57 } 58 return ans; 59 } 60 61 int main(){ 62 rd(n); rd(m); S=0; T=2*n+1; 63 rep(i,1,n) rd(t),add(0,i,1,0),add(i+n,T,1,0),add(0,i+n,1,t); 64 rep(i,1,m){ 65 rd(u),rd(v),rd(c); 66 if (u>v) swap(u,v); add(u,v+n,1,c); 67 } 68 zkw(); printf("%d\n",ans); 69 return 0; 70 }
有上下界的网络流
https://www.cnblogs.com/liu-runda/p/6262832.html
https://www.cnblogs.com/kane0526/archive/2013/04/05/3001108.html
http://blog.csdn.net/clove_unique/article/details/54884437
严谨证明请查阅算法书,推荐第一个网址,浅显易懂。第三个网址中有提到有上下界的费用流。
1.无源汇可行流
增设超级源汇,设$L[i]$为上界,$R[i]$为下界,$D[u]$为$\sum L[v] (<v,u> \in E) - \sum L[v] (<u,v> \in E)$。
对于每条边,连$R[i]-L[i]$的边。
对于每个点,若$D[u]$为负,则从u连$-D[u]$到超级汇,否则从超级源连$D[u]$到u。
以下选自第一个网页
- 无源汇有上下界可行流(也就是循环流)
模型:一个网络,求出一个流,使得每条边的流量必须>=Li且<=Hi,每个点必须满足总流入量=总流出量(流量守恒)(这个流的特点是循环往复,无始无终).
这个算法是有上下界网络流算法的基础,只要深刻理解这个算法其他算法也就水到渠成,因此我用大篇幅力图将这个算法的思想和细节阐述清楚.
可行流算法的核心是将一个不满足流量守恒的初始流调整成满足流量守恒的流.
流量守恒,即每个点的总流入量=总流出量
如果存在一个可行流,那么一定满足每条边的流量都大于等于流量的下限.因此我们可以令每条边的流量等于流量下限,得到一个初始流,然后建出这个流的残量网络.(即:每条边的流量等于这条边的流量上限与流量下限之差)这个初始流不一定满足流量守恒,因此最终的可行流一定是在这个初始流的基础上增大了一些边的流量使得所有点满足流量守恒.
因此我们考虑在残量网络上求出一个另不满足流量守恒的附加流,使得这个附加流和我们的初始流合并之后满足流量守恒.即:
如果某个点在所有边流量等于下界的初始流中满足流量守恒,那么这个点在附加流中也满足流量守恒,
如果某个点在初始流中的流入量比流出量多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之后所加反向边的权值).
1 rep(i,1,m){ 2 u=rd(); v=rd(); low=rd(); up=rd(); 3 add(u,v,up-low); d[v]+=low; d[u]-=low; fl[i]=low; 4 } 5 rep(i,1,n) if (d[i]>0) add(S,i,d[i]),sm+=d[i]; else add(i,T,-d[i]); 6 ans=dinic(); if (ans<sm) { puts("NO"); return 0; } 7 puts("YES"); 8 rep(i,1,m) printf("%d\n",f[(i<<1)|1]+fl[i]);
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最大流,第二次的结果就是答案。
有源汇有上下界最大流
模型:现在的网络有一个源点s和汇点t,求出一个流使得源点的总流出量等于汇点的总流入量,其他的点满足流量守恒,而且每条边的流量满足上界和下界限制.在这些前提下要求总流量最大.
首先套用上面的算法求出一个有源汇有上下界可行流.此时的流不一定最大.
接下来在残量网络上跑s-t最大流即可.
最终的最大流流量=可行流流量(即t到s的无穷边上跑出的流量)+新增广出的s-t流量
问题:会不会增广的时候使得一些边不满足流量下限?
不会.因为我们一开始建的图就是把大小等于流量下限的流量拿出去之后的残量网络,这些流量根本没有在图中出现.
1 n=rd(); m=rd(); s=rd(); t=rd(); S=n+1; T=n+2; cnt=1; 2 rep(i,1,m){ 3 u=rd(); v=rd(); low=rd(); up=rd(); 4 add(u,v,up-low); d[v]+=low; d[u]-=low; fl[i]=low; 5 } 6 rep(i,1,n) if (d[i]>0) add(S,i,d[i]),sm+=d[i]; else add(i,T,-d[i]); 7 add(t,s,inf); ans=dinic(); 8 if (ans<sm) { puts("please go home to sleep"); return 0; } 9 S=s; T=t; printf("%d\n",dinic());
4.有源汇最小流
法一:先不连汇到源的边跑一次超级源汇的最大流,连了汇到源的边之后再跑一次超级源汇的最大流,答案即为边t->s,inf的实际流量.
法二:先连源汇跑可行流,删去t->s边后,再在残量网络上跑t->s最大流,相减得答案。
法三:考虑到法二第二次求出的新增流多算了t->s边上的流,且如果不删t->s边的话,这条边必定被跑满(即inf-可行流),故最小流=可行流-(新增流-(inf-可行流))=inf-新增流。
(和最大流只有最后一行不一样)
1 n=rd(); m=rd(); s=rd(); t=rd(); S=n+1; T=n+2; cnt=1; 2 rep(i,1,m){ 3 u=rd(); v=rd(); low=rd(); up=rd(); 4 add(u,v,up-low); d[v]+=low; d[u]-=low; fl[i]=low; 5 } 6 rep(i,1,n) if (d[i]>0) add(S,i,d[i]),sm+=d[i]; else add(i,T,-d[i]); 7 add(t,s,inf); ans=dinic(); 8 if (ans<sm) { puts("please go home to sleep"); return 0; } 9 S=t; T=s; printf("%d\n",inf-dinic());
5.有上下界的费用流
引用第三个网页的话:
实际上就是费用只在第一种边上的意思。
建图方法
将有上下界的网络流图转化成普通的网络流图
- 首先建立附加源点ss和附加汇点tt
- 对于原图中的边x->y,若限制为[b,c],费用为cost,那么连边x->y,流量为c-b,费用为cost
- 对于原图中的某一个点i,记d(i)为流入这个点的所有边的下界和减去流出这个点的所有边的下界和
连边t->s,流量为inf,费用为0
- 若d(i)>0,那么连边ss->i,流量为d(i),费用为0
- 若d(i)<0,那么连边i->tt,流量为-d(i),费用为0
求解方法
跑ss->tt的最小费用最大流
答案即为(求出的费用+原图中边的下界*边的费用)(BZOJ2502和BZOJ3876)
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=1010,M=30010,inf=1000000000; 7 bool inq[N]; 8 int n,k,v,tim,ss,tt,S,T,cnt=1,ans,h[N],d[N],dis[N],pre[N],q[N],to[M],val[M],nxt[M],fl[M]; 9 inline int inc(int &x){ x++; if (x>n+5) x-=n+5; return x; } 10 11 inline void add(int u,int v,int f,int c){ 12 to[++cnt]=v; fl[cnt]=f; val[cnt]=c; nxt[cnt]=h[u]; h[u]=cnt; 13 to[++cnt]=u; fl[cnt]=0; val[cnt]=-c; nxt[cnt]=h[v]; h[v]=cnt; 14 } 15 16 void work(int u,int v,int low,int high,int c){ 17 add(u,v,high-low,c); d[u]-=low; d[v]+=low; ans+=low*c; 18 } 19 20 bool spfa(){ 21 rep(i,0,n+3) dis[i]=inf,pre[i]=-1,inq[i]=0; 22 inq[S]=1; q[1]=S; dis[S]=0; 23 for (int st=0,ed=1; st!=ed; ){ 24 int x=q[inc(st)]; inq[x]=0; 25 for (int i=h[x],k; i; i=nxt[i]) 26 if (fl[i] && dis[k=to[i]]>dis[x]+val[i]){ 27 dis[k]=dis[x]+val[i]; pre[k]=i; 28 if (!inq[k]) inq[k]=1,q[inc(ed)]=k; 29 } 30 } 31 return dis[T]!=dis[0]; 32 } 33 34 int mcmf(){ 35 int res=0; 36 while (spfa()){ 37 int mn=inf; 38 for (int i=pre[T]; ~i; i=pre[to[i^1]]) mn=min(mn,fl[i]); 39 for (int i=pre[T]; ~i; i=pre[to[i^1]]) fl[i]-=mn,fl[i^1]+=mn; 40 res+=dis[T]*mn; 41 } 42 return res; 43 } 44 45 int main(){ 46 freopen("bzoj3876.in","r",stdin); 47 freopen("bzoj3876.out","w",stdout); 48 scanf("%d",&n); ss=1; tt=n+1; S=n+2; T=n+3; 49 rep(u,1,n){ 50 scanf("%d",&k); 51 rep(i,1,k) scanf("%d%d",&v,&tim),work(u,v,1,inf,tim); 52 if (u>1) work(u,tt,0,inf,0); 53 } 54 rep(i,1,n) if (d[i]>0) add(S,i,d[i],0); else add(i,T,-d[i],0); 55 add(tt,ss,inf,0); printf("%d\n",ans+mcmf()); 56 return 0; 57 }
最小割
最大流最小割定理:最大流=最小割。
使最大流算法的应用范围更广了。经典应用:混合图的欧拉回路。
二分图
最大匹配使用匈牙利算法,最大带权匹配使用KM算法。
可解决一般图的最小路径覆盖问题,二分图的最大独立集问题等。
程序是Pascal的
匈牙利:
1 //匈牙利O(VE) 2 var 3 i,x,y,e,n,s:longint; 4 map:array[1..1010,1..1010]of boolean;//记录读入的边 5 mark:array[1..1010]of boolean;//标记在一个点是否在增广路中 6 link:array[1..1010]of longint;//不是记录增广路,而是记录匹配边 7 8 function find(x:longint):boolean; 9 var i:longint; 10 begin 11 for i:=1 to n do 12 if (map[x,i])and(not(mark[i])) then 13 begin 14 mark[i]:=true; 15 if (link[i]=0)or(find(link[i])) then 16 begin link[i]:=x; exit(true); end; 17 end; 18 exit(false);//表示找不到合适的边,该增广路不合理。 19 20 end; 21 begin 22 fillchar(map,sizeof(map),0); 23 readln(n,e); 24 for i:=1 to e do 25 begin 26 readln(x,y); map[x,y]:=true;//邻接表储存 27 28 end; 29 s:=0; 30 for i:=1 to n do 31 begin 32 fillchar(mark,sizeof(mark),false); 33 if find(i) then inc(s); 34 end; 35 writeln(s); 36 end.
KM算法(有时可以用费用流代替):
1 //KM算法O(n^3) hdu2255: 2 3 #include<cstdio> 4 5 #include<cstring> 6 7 #include<algorithm> 8 9 #define rep(i,l,r) for (int i=l; i<=r; i++) 10 11 using namespace std; 12 13 const int N=310,inf=0x3f3f3f3f; 14 int n,link[N],lx[N],ly[N],visx[N],visy[N],s[N],w[N][N]; 15 16 int dfs(int x){ 17 visx[x]=1; 18 rep(y,1,n){ 19 if (visy[y]) continue; 20 int t=lx[x]+ly[y]-w[x][y]; 21 if (t==0){ 22 visy[y]=1; 23 if (link[y]==-1 || dfs(link[y])) 24 { link[y]=x; return 1;} 25 } 26 else s[y]=min(s[y],t); 27 } 28 return 0; 29 } 30 31 int KM(){ 32 memset(link,-1,sizeof(link)); 33 memset(lx,-inf,sizeof(lx)); 34 memset(ly,0,sizeof(ly)); 35 rep(i,1,n) rep(j,1,n) if (w[i][j]>lx[i]) lx[i]=w[i][j]; 36 rep(x,1,n){ 37 rep(i,1,n) s[i]=inf; 38 while (1){ 39 memset(visx,0,sizeof(visx)); 40 memset(visy,0,sizeof(visy)); 41 if (dfs(x)) break; 42 int d=inf; 43 rep(i,1,n) if (!visy[i] && d>s[i]) d=s[i]; 44 rep(i,1,n) if (visx[i]) lx[i]-=d; 45 rep(i,1,n) if (visy[i]) ly[i]+=d; else s[i]-=d; 46 } 47 } 48 int res=0; 49 rep(i,1,n) if (link[i]!=-1) res+=w[link[i]][i]; 50 return res; 51 } 52 53 int main(){ 54 while (scanf("%d",&n)==1){ 55 rep(i,1,n) rep(j,1,n) scanf("%d",&w[i][j]); 56 printf("%d\n",KM()); 57 } 58 return 0; 59 }
最大权闭合子图
https://www.cnblogs.com/kane0526/archive/2013/04/05/3001557.html
https://www.cnblogs.com/wuyiqi/archive/2012/03/12/2391960.html
例题:网络流24题 之 太空飞行计划问题(见第二个网页)
NOI2006最大获利
例题
网络流24题
https://www.cnblogs.com/xseventh/p/7912202.html
http://blog.csdn.net/ruoruo_cheng/article/details/51815257
https://www.cnblogs.com/gengchen/p/6605548.html
问题编号 |
问题名称 |
问题模型 |
转化模型 |
1 |
飞行员配对方案问题 |
二分图最大匹配 |
网络最大流 |
2 |
太空飞行计划问题 |
最大权闭合图 |
网络最小割 |
3 |
最小路径覆盖问题 |
有向无环图最小路径覆盖 |
网络最大流 |
4 |
魔术球问题 |
有向无环图最小路径覆盖 |
网络最大流 |
5 |
圆桌问题 |
二分图多重匹配 |
网络最大流 |
6 |
最长递增子序列问题 |
最多不相交路径 |
网络最大流 |
7 |
试题库问题 |
二分图多重匹配 |
网络最大流 |
8 |
机器人路径规划问题 |
(未解决) |
最小费用最大流 |
9 |
方格取数问题 |
二分图点权最大独立集 |
网络最小割 |
10 |
餐巾计划问题 |
线性规划网络优化 |
最小费用最大流 |
11 |
航空路线问题 |
最长不相交路径 |
最小费用最大流 |
12 |
软件补丁问题 |
最小转移代价 |
最短路径 |
13 |
星际转移问题 |
网络判定 |
网络最大流 |
14 |
孤岛营救问题 |
分层图最短路径 |
最短路径 |
15 |
汽车加油行驶问题 |
分层图最短路径 |
最短路径 |
16 |
数字梯形问题 |
最大权不相交路径 |
最小费用最大流 |
17 |
运输问题 |
网络费用流量 |
最小费用最大流 |
18 |
分配问题 |
二分图最佳匹配 |
最小费用最大流 |
19 |
负载平衡问题 |
最小代价供求 |
最小费用最大流 |
20 |
深海机器人问题 |
线性规划网络优化 |
最小费用最大流 |
21 |
最长k可重区间集问题 |
最大权不相交路径 |
最小费用最大流 |
22 |
最长k可重线段集问题 |
最大权不相交路径 |
最小费用最大流 |
23 |
火星探险问题 |
线性规划网络优化 |
最小费用最大流 |
24 |
骑士共存问题 |
二分图最大独立集 |
网络最小割 |
[BZOJ3144][HNOI2013]切糕 一种用inf边强制进行某种约束的方法。
个人小结
网络流给人最明显的感觉就是,它是一个彻底的大黑箱。很多时候应对不同的情况时改变的不是执行过程而是建图形式(当然现在也有不少题需要改变算法流程)。这就使网络流问题的建图尤为重要。
可以转化为单纯形问题(对偶原理,资料见我的另一篇单纯形学习笔记)
主要技巧有:拆点(典型应用是当一些点规定只能到达几次时可以通过拆点变成边的流量限制);加inf边(既可以起保护作用(如最小割问题中给每条边连一条同样起点终点的inf边保护),也可以起破坏作用(强制约束,BZOJ2502);先全部确定一个状态,再判断是否需要改变原先状态(混合图的欧拉回路);将流量来源相同或流量去向相同的点合并(POJ1149 PIG);建立分层图(NOI2012 美食节)。
UPD:被 [九省联考2018]劈配 打脸,这题将网络流作为一个随时加边和增广的白箱。