USACO草地排水

USACO草地排水  ------求最大流
题目描述 Description

在农夫约翰的农场上,每逢下雨,Bessie最喜欢的三叶草地就积聚了一潭水。这意味着草地被水淹没了,并且小草要继续生长还要花相当长一段时间。因此,农夫约翰修建了一套排水系统来使贝茜的草地免除被大水淹没的烦恼(不用担心,雨水会流向附近的一条小溪)。作为一名一流的技师,农夫约翰已经在每条排水沟的一端安上了控制器,这样他可以控制流入排水沟的水流量。

农夫约翰知道每一条排水沟每分钟可以流过的水量,和排水系统的准确布局(起点为水潭而终点为小溪的一张网)。需要注意的是,有些时候从一处到另一处不只有一条排水沟。

根据这些信息,计算从水潭排水到小溪的最大流量。对于给出的每条排水沟,雨水只能沿着一个方向流动,注意可能会出现雨水环形流动的情形。

输入描述 Input Description

第1行: 两个用空格分开的整数N (0 <= N <= 200) 和 M (2 <= M <= 200)。N是农夫John已经挖好的排水沟的数量,M是排水沟交叉点的数量。交点1是水潭,交点M是小溪。

第二行到第N+1行: 每行有三个整数,Si, Ei, 和 Ci。Si 和 Ei (1 <= Si, Ei <= M) 指明排水沟两端的交点,雨水从Si 流向Ei。Ci (0 <= Ci <= 10,000,000)是这条排水沟的最大容量。

输出描述 Output Description

输出一个整数,即排水的最大流量。

样例输入 Sample Input
5 4
1 2 40
1 4 20
2 4 20
2 3 30
3 4 10
样例输出 Sample Output

50

算法一:Dinic算法:

Dinic求最大流。这个算法我还是理解得比较好,过程如下:

①建图,注意这里要建立反向边,可以用p^1代表边p的反向边的编号,但这时候就有一个问题,明显0和1是一对反向边,2和3是一对反向边,4和5,6和7。。。2k和2k+1(k∈[0,e-1],e为边数)都分别是一对反向边,而平时用链式前向星存储图时,以p=0为标志,代表已经将所有与这个点相连的边遍历完,但在这里p=0的意义是编号为0的边。为了不让这种混乱的事情发生,我们让边从2开始编号,即程序中给tot赋初值1。(因为这个差错查了一整天啊)

②从起点出发,进行BFS,求出每个点的深度,即距离S点有多少条边。如果发现从S点出发,无法到达T点,则转到⑤

③从起点出发,用DFS找一条增广路进行增广,注意一定要沿着深度递增进行增广,即每个点只能走到比此点深度大1的点,到达T点时,将当前流量计入ans,将经过的每条边的f值加上增广的流量,反向边则减去。继续进行此操作,直到在当前残余网络中无法进行增广(即增广的流量为0)。

④转到②操作

⑤输出ans,程序结束

代码(多次修改了,不太好看。。):

//Dinic
#include <cstdio>
#include <cstring>
#include <algorithm>
#include<queue>
#define maxn 210
#define maxm 410
using namespace std;
int f[maxm], head[maxn], next[maxm], c[maxm], tot=1, to[maxm], N ,M, dep[maxn],
	ans;
queue<int> q;
void add(int u, int v, int w)
{
	to[++tot]=v;
	c[tot]=w;
	next[tot]=head[u];
	head[u]=tot;
}
void input()
{
	int u, v, w, i;
	scanf("%d%d",&M,&N);
	for(i=1;i<=M;i++)
		scanf("%d%d%d",&u,&v,&w),
		add(u,v,w),add(v,u,0);
}
int BFS()
{
	int p, x;
	q.push(1);
	memset(dep,-1,sizeof(dep));
	dep[1]=0;
	while(!q.empty())
	{
		x=q.front();q.pop();
		for(p=head[x];p;p=next[p])
			if(dep[to[p]]==-1 && c[p]-f[p])
			{
				dep[ to[p] ]=dep[x]+1;
				q.push(to[p]);
			}
	}
	return dep[N]!=-1;
}
int dfs(const int x, const int lim)
{
	if(x==N){ans+=lim;return lim;} 
	int p, t, y;
	for(p=head[x];p;p=next[p])
	{
		y=to[p];
		if( dep[y]==dep[x]+1 && c[p]-f[p] && ( t=dfs( y,min(lim,c[p]-f[p]) ) ) )
		    {f[p]+=t;f[p^1]-=t;return t;}
	}
	return 0;
}
int main()
{
	input();
	while( BFS() )
	{
		while( dfs(1,0x7fffffff) );
	}
	printf("%d\n",ans);
	return 0;
}

算法二:ISAP算法:

     这是一个牛逼哄哄的算法,这个比起Dinic好写多了,主要原因是不用BFS来处理d数组,而在dfs中处理它。一开始所有节点的d值都等于0,表示和t的距离是0,然后用dfs找增广路,每次找d值减小1的节点进行增广。如果一个点在当前d值下增广完了,就把它的d值+1。一直进行下去。

      以上只是叫SAP算法。而ISAP是Improved SAP,原因是加了两个优化:

      1、当前弧优化,设一个last数组,表示x这个点上次增广最后用的一条边是last[x],下次再到这里时不从head[x]开始增广,而是从last[x]开始。这里有一点不好理解,一个点在当前d值下增广完成的标志应该是这个点的流量,等于你给它无限大的流入,它流出了多少,换句话说,无论你给它多大的流入,它都只能流出这么多。在程序中怎样判断呢?很明显不好判断,但我们知道如果与它相连的边还没增广完,它的当前流出量已经和流入量相等了,那么这个点就有可能还没流满,这时我们应该直接返回它的流量,避免修改d数组(如果修改会造成答案错误),如果当它遍历完所有点后,还没有return,那说明它已经流满了。可以修改d值,令d[x]++。

      2、GAP优化,设一个num数组,num[i]表示d值为i的节点有多少个,明显地,当我们执行d[x]++之前,先执行num[d[x]]--,表示与t距离为d[x]的点要减少一个,明显地,如果某个num[i]等于0,表示到t距离为i的点已经不存在了,也就是说,这张图“断流了”,从S点无法走到T点了,这时,程序果断退出。

      两个优化都牛逼哄哄,而且很快,能学会这个东西我真是太激动了

代码很短:

#include <cstdio>
#include <algorithm>
#define maxn 1000
#define inf 0x3f3f3f3f
using namespace std;
int N, M, next[maxn], tot=1, head[maxn], to[maxn], c[maxn], d[maxn], num[maxn], Exit,
	last[maxn], S, T;
void adde(int a, int b, int v){to[++tot]=b;c[tot]=v;next[tot]=head[a];head[a]=tot;}
int isap(int pos, int in)
{
	int flow=0, t;
	if(pos==T)return in;
	for(int &p=last[pos];p;p=next[p])
		if(c[p] and d[to[p]]+1==d[pos])
		{
			flow+= t=isap(to[p],min(c[p],in-flow));
			c[p]-=t, c[p xor 1]+=t;
			if(Exit or in==flow)return flow;
		}
	Exit=--num[d[pos]]==0;
	++num[++d[pos]];
	last[pos]=head[pos];
	return flow;
}
int main()
{
	int a, b, v, i, ans=0;
	scanf("%d%d",&M,&N);
	S=1, T=N;
	for(i=1;i<=M;i++)scanf("%d%d%d",&a,&b,&v),adde(a,b,v),adde(b,a,0);
	num[0]=N;
	while(Exit==0)ans+=isap(1,inf);
	printf("%d",ans);
	return 0;
}





  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值