Drainage Ditches
Time Limit: 1000MS Memory Limit: 10000KB
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
解题
题意分析
抛开故事情节,其实就是一个明显的网络流问题。
解法一:Edmonds-Karp算法
概念
求最大流的过程,就是不断找到一条源到汇的路径,然后构建残余网络,再在残余网络上寻找新的路径,使总流量增加,然后形成新的残余网络,再寻找新路径…..直到某个残余网络上找不到从源到汇的路径为止,最大流就算出来了。每次寻找新流量并构造新残余网络的过程, 就叫做寻找流量的“增广路径”,也叫“增广“。
在每次增广的时候,选择从源到汇的具有最少边数的增广路径,即不是通过dfs寻找增广路径,而是通过bfs寻找增广路径。这就是Edmonds-Karp 最短增广路算法。可以证明这种算法的复杂度上限为O(n*m^2)(n是
点数,m是边数)(事实上,楼猪并不会证)
代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
int n,m;
int fa[205];//记录父节点
bool vis[205];//记录是否已访问
const int INF=10000005;
int maxflow[205][205];//存储可用流量
unsigned int find_maxflow()
{
queue<int> q;
memset(fa,0,sizeof(fa));
memset(vis,0,sizeof(vis));
bool find_path=false;
fa[1]=0,vis[1]=1;
int x;
q.push(1);
while(!q.empty()&&!find_path)//队列空了或找到源汇路就退出
{
x=q.front();
q.pop();
for(int i=1;i<=m;i++)
{
if(maxflow[x][i]>0&&!vis[i])//还有可用流量且还未被访问
{
fa[i]=x;
vis[i]=1;
if(i==m)//找到源汇路
{
find_path=true ;
break;
}
else
q.push(i);
}
}
}
if(!find_path)
return 0;
int minflow=INF;
x=m;
//寻找源到汇路径上容量最小的边,其容量就是此次增加的总流量
while(fa[x])
{
minflow=min(minflow,maxflow[fa[x]][x]);
x=fa[x];
}
x=m;
//沿此路径添加反向边,同时修改路径上每条边的容量
while(fa[x])
{
maxflow[fa[x]][x]-=minflow;
maxflow[x][fa[x]]+=minflow;
x=fa[x];
}
return minflow;
}
int main(){
while(~scanf("%d%d",&n,&m)){
memset(maxflow,0,sizeof(maxflow));
for(int i=1;i<=n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
maxflow[u][v]+=w;
}
unsigned int Maxflow=0,Flow;
while(Flow=find_maxflow())
Maxflow+=Flow;
printf("%d\n",Maxflow);
}}
解法二 Dinic算法
概念
先利用BFS对残余网络分层(一个节点的层数,就是源点到它最少要经过的边数)。 分完层后,利用DFS从前一层向后一层反复寻找增广路。 ((即要求DFS的每一步都必须要走到下一层的节点)。 DFS过程中,要是碰到了汇点,则说明找到了一条增广 路径。此时要增加总流量的值,消减路径上各边的容量,并添加反向边,即所谓的进行增广。
DFS找到一条增广路径后,并不立即结束,而是回溯后
继续DFS寻找下一个增广路径。
回溯到哪个节点呢?
回溯到的节点u满足以下条件:
1) DFS搜索树的树边(u,v)上的容量已经变成0 。即刚刚找
到的增广路径上所增加的流量,等于(u,v)本次增广前的容量。(DFS的过程中,是从u走到更下层的v 的)。
2)u是满足条件 1)的最上层的节点
如果回溯到源点而且无法继续往下走了,DFS结束。
因此,一次DFS过程中,可以找到多条增广路径。
DFS结束后,对残余网络再次进行分层,然后再进行DFS。
当残余网络的分层操作无法算出汇点的层次(即BFS到达不了汇点)时,算法结束,最大流求出。
时间复杂度为O(m*n^2)(n是点数,m是边数),是比Edmonds-Karp算法代码量更大但更快的方法,推荐在算法竞赛中选择Dinic。
代码
事实上,这代码并不是楼主自己写的。
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
#include <deque>
using namespace std;
#define INFINITE 999999999
int G[300][300];
bool Visited[300];
int Layer[300]; int n,m; //1是源点,m是汇点
bool CountLayer() {
int layer = 0; deque<int>q;
memset(Layer,0xff,sizeof(Layer)); //都初始化成-1
Layer[1] = 0; q.push_back(1);
while( ! q.empty()) {
int v = q.front();
q.pop_front();
for( int j = 1; j <= m; j ++ ) {
if( G[v][j] > 0 && Layer[j] == -1 ) {
//Layer[j] == -1 说明j还没有访问过
Layer[j] = Layer[v] + 1;
if( j == m ) //分层到汇点即可
return true;
else
q.push_back(j);
}
}
}
return false;
}
int Dinic()
{
int i; int s;
int nMaxFlow = 0;
deque<int> q; //DFS用的栈
while( CountLayer() ) { // 只要能分层
q.push_back(1); //源点入栈
memset(Visited,0,sizeof(Visited)); Visited[1] = 1;
while( !q.empty()) {
int nd = q.back();
if( nd == m ) { // nd是汇点
//在栈中找容量最小边
int nMinC = INFINITE;
int nMinC_vs; //容量最小边的起点
for( i = 1;i < q.size(); i ++ ) {
int vs = q[i-1];
int ve = q[i];
if( G[vs][ve] > 0 ) {
if( nMinC > G[vs][ve] ) {
nMinC = G[vs][ve];
nMinC_vs = vs;
}
}
}
//增广,改图
nMaxFlow += nMinC;
for( i = 1;i < q.size(); i ++ ) {
int vs = q[i-1];
int ve = q[i];
G[vs][ve] -= nMinC; //修改边容量
G[ve][vs] += nMinC; //添加反向边
}
//退栈到 nMinC_vs成为栈顶,以便继续dfs
while( !q.empty() && q.back() != nMinC_vs ) {
Visited[q.back()] = 0;
q.pop_back();
}
}
else { //nd不是汇点
for( i = 1;i <= m; i ++ ) {
if( G[nd][i] > 0 && Layer[i] == Layer[nd] + 1 &&
! Visited[i]) {
// 只往下一层的没有走过的节点走
Visited[i] = 1;
q.push_back(i);
break;
}
if( i > m) //找不到下一个点
q.pop_back(); // 回溯
}
}
}
return nMaxFlow;
}
int main()
{
while (cin >> n >> m ) {
int i,j,k;
int s,e,c;
memset( G,0,sizeof(G));
for( i = 0;i < n;i ++ ) {
cin >> s >> e >> c;
G[s][e] += c; //两点之间可能有多条边
}
cout << Dinic() << endl;
}
return 0;
}
若有不足之处,望多包涵