#include<stdio.h> #include<string.h> #include<iostream> #include<algorithm> #include<queue> using namespace std; int dp[100][100],pre[100]; const int tmin=999999999; int maxflow; void EK(int start,int end,int n){ while(1){ queue<int>q; q.push(1);//源点为1,进队 int minflow=tmin; memset(pre,0,sizeof(pre));//初始化增广路径数组,题目中的顶点是从1开始的 while(!q.empty()){//bfs找增广路 int u=q.front(); q.pop(); for(int i=1;i<=n;i++){ if(dp[u][i]>0&&!pre[i]){//pre[i]除了记录当前顶点的父亲,还记录当前顶点有没被访问过 pre[i]=u; q.push(i); } } } if(pre[end]==0)//顶点的父亲为空,表示找不到增广路,很容易理解吧。。 break; for(int i=end;i!=start;i=pre[i]){//找出增广路中最小残余量 minflow=min(dp[pre[i]][i],minflow); } for(int i=end;i!=start;i=pre[i]){//更新增广路中正反向弧的流量 dp[pre[i]][i]-=minflow; dp[i][pre[i]]+=minflow; } maxflow+=minflow; } } int main(){ int count=0; int n,m; int t; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); memset(dp,0,sizeof(dp)); memset(pre,0,sizeof(pre)); count++; int u,v,w; for(int i=1;i<=m;i++){ scanf("%d%d%d",&u,&v,&w); dp[u][v]+=w; } maxflow=0; EK(1,n,n); printf("Case %d: %d ",count,maxflow); } return 0; }
EdmondsKarp算法,简称EK算法,O(m^2n)
因为是初学教程,所以我会尽量避免繁杂的数学公式和证明。也尽量给出了较为完整的代码。
本文的目标群体是网络流的初学者,尤其是看了各种NB的教程也没看懂怎么求最大流的小盆友们。本文的目的是,解释基本的网络流模型,最基础的最大流求法,即bfs找增广路法,也就是EK法,全名是Edmond-Karp,其实我倒是觉得记一下算法的全名和来历可以不时的拿出来装一装。
比如说这个,EK算法首先由俄罗斯科学家Dinic在1970年提出,没错,就是dinic算法的创始人,实际上他提出的也正是dinic算法,在EK的基础上加入了层次优化,这个我们以后再说,1972年Jack Edmonds和Richard Karp发表了没有层次优化的EK算法。但实际上他们是比1790年更早的时候就独立弄出来了。
你看,研究一下历史也是很有趣的。
扯远了,首先来看一下基本的网络流最大流模型。
有n个点,有m条有向边,有一个点很特殊,只出不进,叫做源点,通常规定为1号点。另一个点也很特殊,只进不出,叫做汇点,通常规定为n号点。每条有向边上有两个量,容量和流量,从i到j的容量通常用c[I,j]表示,流量则通常是f[I,j]。通常可以把这些边想象成道路,流量就是这条道路的车流量,容量就是道路可承受的最大的车流量。很显然的,流量<=容量。而对于每个不是源点和汇点的点来说,可以类比的想象成没有存储功能的货物的中转站,所有”进入”他们的流量和等于所有从他本身”出去”的流量。
把源点比作工厂的话,问题就是求从工厂最大可以发出多少货物,是不至于超过道路的容量限制,也就是,最大流。
比如这个图。每条边旁边的数字表示它的容量。
下面我们来考虑如何求最大流。
首先,假如所有边上的流量都没有超过容量(不大于容量),那么就把这一组流量,或者说,这个流,称为一个可行流。一个最简单的例子就是,零流,即所有的流量都是0的流。
我们就从这个零流开始考虑,假如有这么一条路,这条路从源点开始一直一段一段的连到了汇点,并且,这条路上的每一段都满足流量<容量,注意,是严格的<,而不是<=。那么,我们一定能找到这条路上的每一段的(容量-流量)的值当中的最小值delta。我们把这条路上每一段的流量都加上这个delta,一定可以保证这个流依然是可行流,这是显然的。
这样我们就得到了一个更大的流,他的流量是之前的流量+delta,而这条路就叫做增广路。
我们不断地从起点开始寻找增广路,每次都对其进行增广,直到源点和汇点不连通,也就是找不到增广路为止。当找不到增广路的时候,当前的流量就是最大流,这个结论非常重要。
寻找增广路的时候我们可以简单的从源点开始做bfs,并不断修改这条路上的delta量,直到找到源点或者找不到增广路。
这里要先补充一点,在程序实现的时候,我们通常只是用一个c数组来记录容量,而不记录流量,当流量+1的时候,我们可以通过容量-1来实现,以方便程序的实现。