[网络流系列]网络流基础
网络流是一种独特的图论模型题目,一般一些莫名其妙的题经过精巧的模型转化,就会变成一道网络流的题目。
这种类型的题目很难套标准解,不论是企业测试题还是oi竞赛题,只要涉及到模型转化就会变成毒瘤题。
这类题目的解法我会在模型篇讨论,这一篇主要还是以网络流基础为主。
本博客无图,不是我嫌画得累,是讲这个东西真用不着图,
如果不清楚网络流概念的请先自行查找相关资料。
我们首先来看最大流问题:
给定你一张图,图中的每一条边都有一个流量限制,再给你一个源点s和汇点t,源点的储水量为+∞,问你从源点s最多能流多少水到汇点t。
解决这类问题的常用算法是Edmond-Karp算法和Dinic算法,它们的核心思路都是寻找增广路。
给出增广路的概念:
若流过从s到t中一条路径,流量会增加,则称这条路是一条增广路。
简单分析,我们一条条的找增广路流过去,如果找不到更多增广路了,那我们流量就已经最大了。
简单证明:没有增广路了,流量就不会增加了...所以流量已经最大了...
怎么找增广路呢?这篇博客只会介绍Dinic算法,这也是最常用,平均效率最高的网络流算法。
其实网络流的算法除了上面两种外还有其他的,不过不常用,本文就不多赘述,学有余力的朋友可以去查找相关资料学习。
Dinic算法其实想法很简单,我们使用bfs把流图分层,然后使用dfs寻找增广路。
与Edmond-Karp的区别就是,Dinic是多路增广,而EK是单路增广。
我们建网络图的时候,给每一条边加一条反向的边权为0的边。
加这些边有什么用处呢?这其实是一个很巧妙的想法。
首先呢,流图满足流量守恒,我们添加反向流量,其实就是提供后续调整流的机会。
我们并不知道怎样流增广路可以得到最大流,我们需要每种情况都流一次,然而我们并不可能用回溯来回去换情况流,这样的时间复杂度是指数级的。这个思路的流程是这样的:我们每找到一条增广路,就把路径上的容量减去该路上的最小流量,为什么减的是最小流量呢?因为我们这一条路径最多能流的流量就是这一条路的最小流量,流一遍后我们剩下的容量就要减那么多。然后我们再在它的反向路径上加上最小流量。为什么要加上这最小流量呢?
易懂一些的理解就是,我们第二次增广,再流过这条边的时候,就相当于把走正向边走过来用掉的流量还回去,不走它了,这样就相当于我们换了一种情况来走了(没走这一条路,走了另外的路)。
然后就是算法流程了,首先我们要用bfs来寻找增广路,在bfs的过程中给流图分层。
对于任意的一个点x,我们从s走到x每多走了一个点,层数就加一。
分完层后,对于从x到y的路径,只要满足dep[y]==dep[x]+1,这条路径就在一条最短增广路上面。
我们每次寻找最短的增广路进行增广,如果没法增广了,就可以判断是下面两种情况了:
1. 找到了最大流 2. 存在更长的增广路可以增广
如果是情况2,我们就继续bfs。每次完成后最短增广路长度+1。由于最短路是<n的,所以我们最多只需要执行n-1次bfs就可以得到答案。
我们每次bfs后,都会得到该最短长度的增广路,然后我们用dfs进行增广操作,每次dfs进行多路增广。
最后我们得到最大流的理论复杂度是$O(N^2M)$的,如果是稠密图,即边很多的情况下,Dinic是要比$O(NM^2)$的EK算法跑得快很多的。
接下来给出代码,看不懂的就看注释吧:
#include<bits/stdc++.h> using namespace std; inline int read(){ int data=0,w=1;char ch=0; while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar(); return data*w; } const int N=1e5+10; const int inf=1<<30; struct Edge{ int nxt,to,val; #define nxt(x) e[x].nxt #define to(x) e[x].to #define val(x) e[x].val }e[N<<1]; int head[N],tot=1,maxflow; int n,m,s,t; int dep[N],inque[N]; inline void addedge(int f,int t,int v){ nxt(++tot)=head[f];to(tot)=t;val(tot)=v;head[f]=tot; } inline int bfs(){ memset(inque,0,sizeof inque); memset(dep,0x3f,sizeof dep); dep[s]=0; queue<int> q; q.push(s); while(q.size()){ int x=q.front();q.pop(); inque[x]=0; for(int i=head[x];i;i=nxt(i)){ int y=to(i); if(dep[y]>dep[x]+1&&val(i)){//最短且增广 dep[y]=dep[x]+1; if(!inque[y]){ q.push(y);inque[y]=1; } } } }return dep[t]!=0x3f3f3f3f; } inline int dfs(int x,int flow){ int rlow=0; if(x==t){ maxflow+=flow;return flow; } int used=0; for(int i=head[x];i;i=nxt(i)){ int y=to(i); if(dep[y]==dep[x]+1&&val(i)){//最短增广路 rlow=dfs(y,min(flow-used,val(i)));//计算剩余流量 if(rlow){ used+=rlow; val(i)-=rlow;//正向边容量 val(i^1)+=rlow;//反向边容量 if(used==flow)break;//流量满了,不找了 } } }if(used==0)dep[x]=0; return used; } inline int Dinic(){ while(bfs())dfs(s,inf); return maxflow; } int main(){ n=read();m=read();s=read();t=read(); for(int i=1;i<=m;i++){ int x=read(),y=read(),z=read(); addedge(x,y,z);addedge(y,x,0); } printf("%d\n",Dinic()); return 0; }
二分图最大匹配:
做法?新建一个超级源点s和超级汇点t。然后s和一边连边,t和另一边连边,再跑一遍最大流即可。
记录方案?判断边是否有流量即可。怎么判断边是否有流量?判断反向边的流量是不是0就ok了。
对于这道题,我们把可以配合的外籍和英籍连边,s向外籍连边,英籍再向t连边跑一遍最大流即可,注意每条边的容量都是1,因为我们只能一一对应地匹配。
给出代码:
#include<bits/stdc++.h> using namespace std; inline int read(){ int data=0,w=1;char ch=0; while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar(); return data*w; } const int N=1e5+10; const int inf=1<<30; struct Edge{ int nxt,to,val; #define nxt(x) e[x].nxt #define to(x) e[x].to #define val(x) e[x].val }e[N<<1]; int head[N],tot=1,maxflow; inline void addedge(int f,int t,int val){ nxt(++tot)=head[f];to(tot)=t;val(tot)=val;head[f]=tot; } int n,m,s,t; int inque[N],dep[N]; int bfs(){ memset(inque,0,sizeof inque); memset(dep,0x3f,sizeof dep); queue<int> q;q.push(s);dep[s]=1; while(q.size()){ int x=q.front();q.pop();inque[x]=0; for(int i=head[x];i;i=nxt(i)){ int y=to(i); if(dep[y]>dep[x]+1&&val(i)){ dep[y]=dep[x]+1; if(!inque[y]){ q.push(y);inque[y]=1; } } } }return dep[t]!=0x3f3f3f3f; } int dfs(int x,int flow){ int rlow=0; if(x==t){ maxflow+=flow;return flow; } int used=0; for(int i=head[x];i;i=nxt(i)){ int y=to(i); if(dep[y]==dep[x]+1&&val(i)){ rlow=dfs(y,min(flow-used,val(i))); if(rlow){ used+=rlow; val(i)-=rlow; val(i^1)+=rlow; if(used==flow)break; } } }if(used==0)dep[x]=0; return used; } int Dinic(){ while(bfs())dfs(s,inf); return maxflow; } int main(){ m=read();n=read();//迷惑题目m,n非得反过来 s=n+1,t=n+2; for(int i=1;i<=m;i++) addedge(s,i,1),addedge(i,s,0);//外国 for(int i=m+1;i<=n;i++) addedge(i,t,1),addedge(t,i,0);//英国 while(1){ int x=read(),y=read(); if(x==-1&&y==-1)break; addedge(x,y,1);addedge(y,x,0); } int ans=Dinic(); if(ans==0) puts("No Solution!"); else{ printf("%d\n",ans); for(int i=2;i<=tot;i+=2){ if(to(i)!=s&&to(i^1)!=s) if(to(i)!=t&&to(i^1)!=t) if(val(i^1)) printf("%d %d\n",to(i^1),to(i)); } } return 0; }
然后是最小费用最大流。
待填坑,先放代码。
#include<bits/stdc++.h> using namespace std; const int maxn=100010; struct Edge{ int nxt,to,val,w; #define nxt(x) e[x].nxt #define to(x) e[x].to #define v(x) e[x].val #define w(x) e[x].w }e[maxn<<1]; const int inf=1<<30; int head[maxn]; int tot=1; int n,m,s,t; int cost,maxflow; int vis[maxn],dis[maxn],cur[maxn]; inline void addedge(int f,int t,int val,int ww){ nxt(++tot)=head[f];to(tot)=t;v(tot)=val;w(tot)=ww;head[f]=tot; } bool spfa(){ for(int i=0;i<=n;i++)cur[i]=head[i],dis[i]=0x3f3f3f3f,vis[i]=0; dis[s]=0;vis[s]=1; queue<int> q;q.push(s); while(q.size()){ int x=q.front();q.pop(); vis[x]=0; for(int i=head[x];i;i=nxt(i)){ int y=to(i); if(dis[y]>dis[x]+w(i) && v(i)){ dis[y]=dis[x]+w(i); if(!vis[y]){ q.push(y); vis[y]=1; } } } } return dis[t]!=0x3f3f3f3f; } inline int Fmin(int x,int y){ return (((y-x)>>31)&(x^y))^x; } inline int Fmax(int x,int y){ return (((y-x)>>31)&(x^y))^y; } int dfs(int x,int flow){ if(x==t){ vis[t]=1;maxflow+=flow;return flow; } int used=0; vis[x]=1; for(int i=cur[x];i;i=nxt(i)){ cur[x]=i; int y=to(i); if((!vis[y]||y==t) && v(i) && dis[y]==dis[x]+w(i)){ int minflow=dfs(y,Fmin(flow-used,v(i))); if(minflow)cost+=w(i)*minflow,v(i)-=minflow,v(i^1)+=minflow,used+=minflow; if(used==flow)break; } } return used; } int MinCostMaxFlow(){ while(spfa()){ vis[t]=1; while(vis[t]){ memset(vis,0,sizeof vis); dfs(s,inf); } } return maxflow; } inline int read(){ int data=0,w=1;char ch=0; while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar(); return data*w; } int main(){ n=read();m=read();s=read();t=read(); for(int i=1;i<=m;i++){ int x=read();int y=read();int val=read();int w=read(); addedge(x,y,val,w); addedge(y,x,0,-w); } maxflow=MinCostMaxFlow(); printf("%d %d",maxflow,cost); return 0; }