下面是由一道题引发的一系列故事。。。
题目链接 http://poj.org/problem?id=1273
Drainage Ditches
Time Limit: 1000MS
Memory Limit: 10000K
Total Submissions: 68920
Accepted: 26683
Description
Every time it rains on Farmer John’s fields, a pond forms over Bessie’s favorite clover patch. This means that the clover is covered by water for awhile and takes quite a long time to regrow. Thus, Farmer John has built a set of drainage ditches so that Bessie’s clover patch is never covered in water. Instead, the water is drained to a nearby stream. Being an ace engineer, Farmer John has also installed regulators at the beginning of each ditch, so he can control at what rate water flows into that ditch.
Farmer John knows not only how many gallons of water each ditch can transport per minute but also the exact layout of the ditches, which feed out of the pond and into each other and stream in a potentially complex network.
Given all this information, determine the maximum rate at which water can be transported out of the pond and into the stream. For any given ditch, water flows in only one direction, but there might be a way that water can flow in a circle.
Input
The input includes several cases. For each case, the first line contains two space-separated integers, N (0 <= N <= 200) and M (2 <= M <= 200). N is the number of ditches that Farmer John has dug. M is the number of intersections points for those ditches. Intersection 1 is the pond. Intersection point M is the stream. Each of the following N lines contains three integers, Si, Ei, and Ci. Si and Ei (1 <= Si, Ei <= M) designate the intersections between which this ditch flows. Water will flow through this ditch from Si to Ei. Ci (0 <= Ci <= 10,000,000) is the maximum rate at which water will flow through the ditch.
Output
For each case, output a single integer, the maximum rate at which water may emptied from the pond.
Sample Input
5 4
1 2 40
1 4 20
2 4 20
2 3 30
3 4 10
Sample Output
50
直接看输入输出。输入:n和m,分别代表边的数量和节点数。之后的n行,输入三个数,代表(u,v)边的最大水容量。要求输出,从起点1开始到终点m的最大水流量。那么,与实际相联系,很容易知道,对于每一条路径,都最大只能运输该路径上的最小容量的那条边所能承受的水量。所以,这个问题就是传说中的网络流之最大流问题。
经过查阅资料,先理清一下基本的概念:
容量网络:设G(V,E),是一个有向网络,在V中指定了一个顶点,称为源点(记为Vs),以及另一个顶点,称为汇点(记为Vt);对于每一条弧
// 用BFS来判断从结点s到t的路径上是否还有delta
// 即判断s,t之间是否还有增广路径,若有,返回1
bool BFS(int s, int t)
{
queue<int> que;
memset(pre, -1, sizeof(pre));
memset(vis, false, sizeof(vis));
pre[s] = s;
vis[s] = true;
que.push(s);
int p;
while(!que.empty())
{
p = que.front();
que.pop();
for(int i=1; i<=M; ++i)
{
if(r[p][i]>0 && !vis[i])
{
pre[i] = p;
vis[i] = true;
if(i == t) // 存在增广路径
return true;
que.push(i);
}
}
}
return false;
}
但事实上并没有这么简单,上面所说的增广路还不完整,比如说下面这个网络流模型。
我们第一次找到了1-2-3-4这条增广路,这条路上的delta值显然是1。于是我们修改后得到了下面这个流。(图中的数字是容量)
改成0是因为这里加上delta后,剩余容量为0。也就是更新为了剩余容量
这时候(1,2)和(3,4)边上的流量都等于容量了,我们再也找不到其他的增广路了,当前的流量是1。
但这个答案明显不是最大流,因为我们可以同时走1-2-4和1-3-4,这样可以得到流量为2的流。
那么我们刚刚的算法问题在哪里呢?问题就在于我们没有给程序一个”后悔”的机会,应该有一个不走(2-3-4)而改走(2-4)的机制。那么如何解决这个问题呢?回溯搜索吗?那么我们的效率就上升到指数级了。
而这个算法神奇的利用了一个叫做反向边的概念来解决这个问题。即每条边(I,j)都有一条反向边(j,i),反向边也同样有它的容量。
我们直接来看它是如何解决的:
在第一次找到增广路之后,在把路上每一段的容量减少delta的同时,也把每一段上的反方向的容量增加delta。即在Dec(c[x,y],delta)的同时,inc(c[y,x],delta)
我们来看刚才的例子,在找到1-2-3-4这条增广路之后,把容量修改成如下
一开始由于不能反向流所以默认反向的容量都是0。在修改时比如1->2减少了1,所以2->1就增加了1.而1->3减少了0,所以3->1也增加0不变,容量还是0.
这时再找增广路的时候,就会找到1-3-2-4这条可增广量,即delta值为1的可增广路。将这条路增广之后,得到了最大流2。
那么,这么做为什么会是对的呢?我来通俗的解释一下吧。
事实上,当我们第二次的增广路走3-2这条反向边的时候,就相当于把2-3这条正向边已经是用了的流量给”退”了回去,不走2-3这条路,而改走从2点出发的其他的路也就是2-4。(有人问如果这里没有2-4怎么办,这时假如没有2-4这条路的话,最终这条增广路也不会存在,因为他根本不能走到汇点)同时本来在3-4上的流量由1-3-4这条路来”接管”。而最终2-3这条路正向流量1,反向流量1,等于没有流量。理解为2,3都有流入为1的水流,相互抵消,这条路断掉了,所以3-4上的流量由1-3-4这条路接管。
这就是这个算法的精华部分,利用反向边,使程序有了一个后悔和改正的机会。而这个算法和我刚才给出的代码相比只多了一句话而已。
至此,最大流Edmond-Karp算法介绍完毕。
Edmond Karp算法具体实现(C/C++)
#include
#include
#include
using namespace std;
const int msize = 205;
int N, M; // N--路径数, M--结点数
int r[msize][msize]; //
int pre[msize]; // 记录结点i的前向结点为pre[i]
bool vis[msize]; // 记录结点i是否已访问
// 用BFS来判断从结点s到t的路径上是否还有delta
// 即判断s,t之间是否还有增广路径,若有,返回1
bool BFS(int s, int t)
{
queue<int> que;
memset(pre, -1, sizeof(pre));
memset(vis, false, sizeof(vis));
pre[s] = s;
vis[s] = true;
que.push(s);
int p;
while(!que.empty())
{
p = que.front();
que.pop();
for(int i=1; i<=M; ++i)
{
if(r[p][i]>0 && !vis[i])
{
pre[i] = p;
vis[i] = true;
if(i == t) // 存在增广路径
return true;
que.push(i);
}
}
}
return false;
}
int EK(int s, int t)
{
int maxflow = 0, d;
while(BFS(s, t))
{
d= INT_MAX;
// 若有增广路径,则找出最小的delta
for(int i=t; i!=s; i=pre[i])
d = min(d, r[pre[i]][i]);
// 这里是反向边,看讲解
for(int i=t; i!=s; i=pre[i])
{
r[pre[i]][i] -= d;
r[i][pre[i]] += d;
}
maxflow += d;
}
return maxflow;
}
int main()
{
while(cin >> N >> M)
{
memset(r, 0, sizeof(r));
int s, e, c;
for(int i=0; icin >> s >> e >> c;
r[s][e] += c; // 有重边时则加上c
}
cout << EK(1, M) << endl;
}
return 0;
}