【前言】
网络流作为一个经典问题,在OI及实际生活中有着广泛的应用,值得我们仔细研究。
【何为网络流?】
网络流,是一种资源调配问题,如下图(以下图片均来自网络):
其中,S表示网络流中的源点,是资源的唯一出发点。
T表示网络流中的终点,是各种资源的目的地。
正如水管有粗细之分,道路有宽窄之分,
网络流中的每条边(这里称为弧)都有一个容量cap,表示单位时间最多能通过的资源量。
同时,每条弧都有一个流量flow,表示当前单位时间通过的资源量。
上图是流的一个可行方案。我们的目的就是要求最大流(单位时间到达终点的资源最大)
【后向弧】
我们定义后向弧为与前向弧顶点相同,方向相反的弧。且后向弧的流量永远为所对应前向弧的流量的相反数。(这里推荐一个实用方法:把边从2开始编号,那么x与x^1就是一对前向/后向弧)
【增广路】
所谓增广路,就是能使当前结果更优的一条路。在最大流中,增广路的定义如下:
1.增广路由源点出发,终点结束
2.增广路走过的每一条弧中,cap>flow(即这条路必须所有弧都可以增加流量)
3.增广路可以经过前向弧,也可以经过后向弧。
上图是一条增广路。其中BC是一条后向弧。同时也发现原来的状态不是最大流。
我们可以发现,后向弧为算法提供了纠正原来错误的可能。
【FF方法】
解决最大流的常见方法是FF方法,FF方法可以由很多不同搜索算法实现,衍生出很多不同的算法。
FF方法的过程:
1.初始时,各弧流量为0。
2.寻找一条增广路,找不到增广路则说明已经获得最大流,退出
3.若找到增广路,取路上各弧的最小残余流量(cap-flow),记为Min
4.把增广路上各弧流量+Min,其对应弧流量作相应修改
5.重复2~4步骤,直至找不到增广路
我们可以发现,FF方法其实就是一个不断纠正,不断优化的过程。其中寻找增广路是次方法的时间瓶颈。
【EK算法】
实际应用时,我们常用BFS来寻找增广路,称为EK算法。
bool bfs(){
memset(vis,0,sizeof(vis));
int hed=0,til=1;
que[1]=s;vis[s]=1;
while (hed!=til)
for (int j=a.lnk[que[++hed]];j;j=a.nxt[j])
if (!vis[a.son[j]]&&a.cap[j]>a.flw[j]){
que[++til]=a.son[j];vis[a.son[j]]=1;
fa[a.son[j]]=que[hed];id[a.son[j]]=j;
if (a.son[j]==t) return 1;
}
return 0;
}
void EK(){
while (bfs()){
int Min=0x3f3f3f3f;
for (int x=t;x!=s;x=fa[x])
Min=min(Min,a.cap[id[x]]-a.flw[id[x]]);
ans+=Min;
for (int x=t;x!=s;x=fa[x])
a.flw[id[x]]+=Min,a.flw[id[x]^1]-=Min;
}
}
显然,在最坏情况下,时间复杂度O(NE2)
【Dinic算法】
实际运用中,我们还有一个更为快捷的算法求解最大流问题,就是Dinic。
【层次图】
我们把源点到i所经过的最少边数称为i的距离。
距离相同的点归为同一个层次。
在残量网络中,只留下连向下一个层次的边,就构成了一个层次图。
【Dinic流程】
首先,如果有层次图,就说明当前解还可以增广
用BFS刷出层次图,然后就可以直接DFS增广
BFS很简单,直接遍历一下就可以了,不用多说
DFS的过程,可以理解为有一杯无限多的水,不断从源点倒入
模拟水流的扩张,遍历整个子图,并返回当前子图能进入的最大流量
代码是这样的:
int dfs(int x,int flow){
if (x==T||flow==0) return flow;
int res=0,f;
for (int j=lnk[x];j;j=nxt[j])
if (d[x]+1==d[son[j]]&&(f=dfs(son[j],min(flow,cap[j]-flw[j])))>0){
flw[j]+=f;flw[j^1]-=f;
res+=f;flow-=f;
if (flow==0) break;
}
return res;
}
【当前弧优化】
其实,上面给出的代码并不是效率最高的。
试想,对于点x,它的一个儿子s如果已经被DFS增广过了
那么下次增广x的时候,就没有必要再来增广s。
因为DFS增广尽可能地使流量最大,再次DFS就不会再有增广的可能
所以我们每次创建lnk[]的一个副本pos[]
那么直接修改pos[]就能达到目的
正确姿势:
int dfs(int x,int flow){
if (x==T||flow==0) return flow;
int res=0,f;
for (int &j=pos[x];j;j=nxt[j])
if (d[x]+1==d[son[j]]&&(f=dfs(son[j],min(flow,cap[j]-flw[j])))>0){
flw[j]+=f;flw[j^1]-=f;
res+=f;flow-=f;
if (flow==0) break;
}
return res;
}
完整模板如下(hihoCoder 1369):
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=505,maxe=40004,INF=0x3f3f3f3f;
int n,e,S,T;
int tot,lnk[maxn],nxt[maxe],cap[maxe],flw[maxe],son[maxe];
void add(int x,int y,int w){
son[++tot]=y;cap[tot]=w;flw[tot]=0;nxt[tot]=lnk[x];lnk[x]=tot;
}
inline int red(){
int tot=0,f=1;char ch=getchar();
while (ch<'0'||'9'<ch) {if (ch=='-') f=-f;ch=getchar();}
while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
return tot*f;
}
int d[maxn],pos[maxn],que[maxn];
bool bfs(){
memset(d,63,sizeof(d));
int hed=0,til=1;
d[S]=0;que[1]=S;
while (hed!=til)
for (int j=lnk[que[++hed]];j;j=nxt[j])
if (d[son[j]]==INF&&flw[j]<cap[j])
que[++til]=son[j],d[son[j]]=d[que[hed]]+1;
return d[T]!=INF;
}
int dfs(int x,int flow){
if (x==T||flow==0) return flow;
int res=0,f;
for (int &j=pos[x];j;j=nxt[j])
if (d[x]+1==d[son[j]]&&(f=dfs(son[j],min(flow,cap[j]-flw[j])))>0){
flw[j]+=f;flw[j^1]-=f;
res+=f;flow-=f;
if (flow==0) break;
}
return res;
}
int main(){
n=red(),e=red();S=1;T=n;tot=1;
for (int i=1,x,y,z;i<=e;i++)
x=red(),y=red(),z=red(),add(x,y,z),add(y,x,0);
int ans=0;
while (bfs()){
memcpy(pos,lnk,sizeof(lnk));
ans+=dfs(S,INF);
}
printf("%d",ans);
return 0;
}

本文介绍了网络流问题的基本概念,包括网络流、后向弧、增广路等,并详细讲解了FF方法、EK算法以及Dinic算法的原理和应用。特别是Dinic算法,通过层次图和DFS进行最大流的求解,同时探讨了当前弧优化的技巧,提高了算法效率。
441





