看了好几天最大流的EK算法了,到今天才总算想明白,趁热打铁写个总结巩固一下。
网络流中的最大流问题指的是给你一个网络,让你求出这个网络中从源点到汇点的最大流量。
可以将其看成管道,每个管道有一个容量和流量,同时有一个残余流量的概念。
比如,设有一条边 u->v ;用 c [u,v] 表示这条边的容量,f [u,v] 表示这条路的流量,则这条边的残余流量 r [u,v] = c [u,v] - f [u,v] ;
EK算法的核心,就是不断地寻找增广路。这里的增广路指的是从源点到汇点的一条可行路,即其中每条边的残余流量均大于0;
当不能再找到增广路时,算法结束,此时源点到汇点之间再没有可行路了,显然流量已经达到了最大值。
通俗一点讲,就是再没有一条路从源点到汇点了,那么自然没有更多流量可以流入到汇点了。
算法的基本思路:通过BFS寻找增广路,每次寻找一条并由此更新整个网络中的残余流量,直至无法找到增广路为止。
hdu 的1532 和 3549 都是模板题,可以拿来练手,附上代码,有注释帮助理解。
hdu 1532
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
#define S 210
#define maxn 0xfffffff
int c[S][S]; //这里表示残余流量
int pre[S],flow[S]; //前驱、到某个点的流量
int N,M; //此题中 M 即为汇点
queue<int>q;
int bfs()
{
memset(pre,-1,sizeof(pre));
while(!q.empty()) q.pop();
flow[1]=maxn; //源点视为无限流出
q.push(1); //源点入列
while(!q.empty())
{
int now=q.front();
q.pop();
if(now==M) break; //当前点为汇点时意味着寻找增广路结束
for(int i=1;i<=M;i++)
{
if(i!=1&&c[now][i]>0&&pre[i]==-1) //回流到源点无意义,此题中 1 为源点
{ //残余流量必须大于0,同时前驱要求为-1,因为算法每次只寻找一条增广路
pre[i]=now; //若该点前驱不为-1意味着已经找到了一条从某个点到该点的增广路,
flow[i]=min(flow[now],c[now][i]); //寻找增广路中允许通过的最大流量
q.push(i); //该点入列
}
}
}
if(pre[M]==-1) return -1; //没有前驱意味着没有找到可行的增广路,返回-1
return flow[M];
}
int maxflow()
{
int add=0,sum=0;
while((add=bfs())!=-1) //add为增广路中可增加的流量,即该条增广路允许的最大流量
{
int k=M;
while(k!=1) //1为源点,利用前驱更新增广路中的残余流量
{
c[pre[k]][k]-=add;
c[k][pre[k]]+=add; //反向边
k=pre[k];
}
sum+=add;
}
return sum;
}
int main()
{
while(~scanf("%d%d",&N,&M))
{
memset(c,0,sizeof(c));
memset(flow,0,sizeof(flow));
int from,to,cap;
for(int i=0;i<N;i++)
{
scanf("%d%d%d",&from,&to,&cap);
c[from][to]+=cap; //注意可能有重边,多条从某个点到另一点的边可将它们相加
}
cout<<maxflow()<<endl;
}
}
hdu 3549
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define S 20
int N,M,c[S][S],flow[S],pre[S];
queue<int>q;
int bfs()
{
while(!q.empty()) q.pop();
memset(pre,-1,sizeof(pre));
flow[1]=0xfffffff;
q.push(1);
while(!q.empty())
{
int now=q.front();
q.pop();
if(now==N) break;
for(int i=1;i<=N;i++)
{
if(i!=1&&c[now][i]>0&&pre[i]==-1)
{
pre[i]=now;
flow[i]=min(flow[now],c[now][i]);
q.push(i);
}
}
}
if(pre[N]==-1) return -1;
return flow[N];
}
int maxflow()
{
int sum=0,add=0;
while((add=bfs())!=-1)
{
int k=N;
while(k!=1)
{
c[pre[k]][k]-=add;
c[k][pre[k]]+=add;
k=pre[k];
}
sum+=add;
}
return sum;
}
int main()
{
int T;
cin>>T;
int K=0;
while(T--)
{
scanf("%d%d",&N,&M);
memset(c,0,sizeof(c));
memset(flow,0,sizeof(flow));
int x,y,cap;
for(int i=0;i<M;i++)
{
scanf("%d%d%d",&x,&y,&cap);
c[x][y]+=cap;
}
printf("Case %d: %d\n",++K,maxflow());
}
}
关于其中反向边的问题,记得看到过一句话:反向边就是给程序一个反悔的机会。
推荐一个博客,可供参考:传送门