HDU1532(Drainage Ditches)

Drainage Ditches

Problem 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

思路

网络流第一题,可以使用FF,EK,Dinic,ISAP。反正这道题是练习使用模板的题目。这道题的点非常少,没有必要用邻接表或者链式前向星,毕竟邻接矩阵好操作。

  1. FF 和 EK的本质区别FF是使用DFS深度优先寻找增广路,EK是使用BFS广度优先找增广路。其他的都是一样的,但是FF是非常容易到达复杂度上限的,而BFS要好很多。找增广路的时候记录父节点,记录最小可行流。然后从终点到起点的路径依次减掉可行流,反向路径加上可行流。重复寻找增广路直到找不到增广路算法结束。构造反向边是因为增广路可能不是一次解决到位的,可以利用反向边进行所谓的 “后悔”。

第一步沿着红线增广
在这里插入图片描述
第二步沿着绿线增广
在这里插入图片描述
如果走了中间这条线且没有反向边的时答案只有100,但是很明显答案应该是不走中间可以获得200.这时候加入反向边就能进行所谓的"后悔",可以继续增广100的流量。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 205;
int map[maxn][maxn]; 
int pre[maxn],flow[maxn];
int bfs(int s,int t)
{
    memset(pre,-1,sizeof(pre));
    queue<int>q;    
    q.push(s);    
    int f = inf;
    while(!q.empty()){
        int x = q.front();
        q.pop();
        if(x == t)        break;
        for(int i = 1;i <= t;i++){
            if(i != s && map[x][i] > 0 && pre[i] == -1){
                pre[i] = x;
                f = min(f,map[x][i]);			//记录最小可行流
                q.push(i);
            }
        }
    }
    if(pre[t] == -1){
        return -1;
    }
    return f;
}
int edmond_karp(int s,int t)
{
    int sum = 0;
    while(true){
        int f = bfs(s,t);
        if(f == -1)        break;
        int x = t;
        while(x != s){
            map[pre[x]][x] -= f;			//正向减掉可行流
            map[x][pre[x]] += f; 			//反向增加可行流
            x = pre[x];						//回溯
        }
        sum += f;
    }
    return sum;
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        memset(map,0,sizeof(map));
        int x,y,z;
        for(int i = 0;i < n;i++){
            scanf("%d%d%d",&x,&y,&z);
            map[x][y] += z;
        }
        int ans = edmond_karp(1,m);
        printf("%d\n",ans);
    }
    return 0;
} 
  1. Dinic算法和EK都是利用增广路的思想进行,但是 Dinic对比EK的优势在于Dinic是将点分层。然后一层一层的靠近汇点。如下图所示,如果走中间这条线EK要走很多很多次,中间这条边来回的加入反向边。那么需要200次增广才能搞完这是比较坏的情况。但是Dinic的做法就很聪明了,为了避免这种尴尬的情况出现,利用BFS将每一个点分层。dep[1] = 0,dep[2] = dep[3] = 2,dep[4] = 3。分完层次之后跑DFS,让DFS沿着深度每次增加1的路径进行增广。那么针对下面这个图两次单次增广就完事了。当然DFS可以同时进行多路同时增广跑一次就够。从而避免了EK的尴尬局面。
    在这里插入图片描述
    一般的Dinic增广,一般的Dinic是一次BFS + n次DFS增广,搜到一条增广路就退出搜索。
int dfs(int s,int cost,int t)
{
	if(s == t){			//到达终点返回最小的流量 
		return cost;			
	}
	for(int i = 1;i <= t;i++){
		if(map[s][i] && dep[i] == dep[s]+1){		//单次增广 
			int flow = dfs(i,min(cost,map[s][i]),t);
			if(flow){					
				map[s][i] -= flow;
				map[i][s] += flow;
				return flow;						//找到一条路即退出
			} 
		}
	}
	return 0;
} 

Dinic多路增广优化:由于DFS是深度优先自带回溯功能所以可以利用这个特性将Dinic改为1次BFS + 1次DFS。而这一次的DFS必须增广完所有的路将流量加起来最后在退出。小优化的含义是如果当前点x有多条增广路径,如果一条增广路径已经将流向x点的可行流全部用完,那么剩下的从x点出发的增广路径就没有可行流了即可继续返回到x点的父节点去。

Dinic炸点优化:如果当前 x点伸展完后发现过这个点没有任何增广路被发现,即当前点可行流值sum为 0,则说明增广路不可能通过该点伸展,故直接把这个点 “炸” 掉,即 dep[u] = -1 。不过我感觉炸点的效果不明显,特别是针对邻接矩阵这种对点的访问有顺序的图。但是针对链式前向星的效果还是挺明显的。

int dfs(int s,int flow,int t)
{
	if(s == t){
		return flow;
	}
	int sum = 0;
	for(int i = 1;i <= t;i++){
		if(map[s][i] && dep[i] == dep[x]+1){
			int k = dfs(i,min(flow-sum,map[s][i]),t);
			if(k){
				map[s][i] -= k;	
				map[i][s] += k;	
				sum += k;		
				if(sum == flow)		break;		//小优化,可以不写。
			}
		}
	}
	if(!sum)	dep[s] = -1;					//炸点优化
	return sum;
}

Dinic还有当前弧优化 和 gap优化,暂时不会留个坑。以及ISAP算法。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
using namespace std; 
const int inf = 0x3f3f3f3f;
const int maxn = 205;
int map[maxn][maxn];
int dep[maxn];
int bfs(int s,int t)				//bfs分层 
{
	memset(dep,0,sizeof(dep));
	queue<int>q;	
	dep[s] = 1;	q.push(s);
	while(!q.empty()){
		int x = q.front();
		q.pop();
		for(int i = 1;i <= t;i++){
			if(map[x][i] && !dep[i]){
				dep[i] = dep[x] + 1;
				q.push(i);
			}
		}
	}
	return dep[t];
}
/*
int dfs(int s,int cost,int t)
{
	if(s == t){			//到达终点返回最小的流量 
		return cost;			
	}
	for(int i = 1;i <= t;i++){
		if(map[s][i] && dep[i] == dep[s]+1){		//单次增广 
			int flow = dfs(i,min(cost,map[s][i]),t);
			if(flow){					
				map[s][i] -= flow;
				map[i][s] += flow;
				return flow;
			} 
		}
	}
	return 0;
} 
*/
int dfs(int s,int flow,int t)			//多路增广
{
	if(s == t)		return flow;
	int sum = 0;
	for(int i = 1;i <= t;i++){
		if(map[s][i] && dep[i] == dep[s]+1){
			int k = dfs(i,min(flow-sum,map[s][i]),t);
			if(k){
				map[s][i] -= k;
				map[i][s] += k;
				sum += k;
				if(sum == flow)		break;		//小优化
			}
		}
	}
	if(!sum)		dep[s] = -1;			//炸点
	return sum;
}
int Dinic(int s,int t)
{
	int sum = 0;
	while(bfs(s,t)){
		sum += dfs(s,inf,t);
	}
	return sum;
}
int main()
{
	int n,m;
	while(~scanf("%d%d",&n,&m)){
		memset(map,0,sizeof(map));
		for(int i = 0;i < n;i++){
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			map[x][y] += z;
		}
		int ans = Dinic(1,m);
		printf("%d\n",ans);
	}
	return 0;
}

愿你走出半生,归来仍是少年~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值