USACO 93 Drainage Ditches

本文介绍了解决最大流问题的三种算法:Edmonds-Karp算法、SAP算法和Dinic算法。通过实例展示了如何使用这些算法来求解复杂网络中的最大流问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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算法

#include<cstdio>
#include<queue>
#include<cstring>

using namespace std;
typedef long long LL;

#define INF 0x3f3f3f3f

LL flow[210][210],M,N,cap[210][210],father[210],a[210];//cap容量 当前flow流量

void readdata()
{
	memset(flow,0,sizeof(flow));
	memset(cap,0,sizeof(cap));
	
	for(int i=1;i<=M;i++)
	{
		LL a,b,c;
		scanf("%I64d%I64d%I64d",&a,&b,&c);
		cap[a][b]+=c;//注意有重边
	}
}

void EK()
{
	memset(flow,0,sizeof(flow));
	LL ans=0;

	while(1)
	{
		memset(a,0,sizeof(a)); a[1]=INF;//a[i]表示1到i的增广量
		queue<LL> q;
		q.push(1);

		while(!q.empty())//BFS找到的增广路一定是最短的(即边数最少)
		{
			int now=q.front();q.pop();
			for(LL i=1;i<=N;i++)
				if(!a[i] && cap[now][i]>flow[now][i])
				{
					father[i]=now;
					q.push(i);
					a[i]=min(a[now],cap[now][i]-flow[now][i]);
				}
		}
		if(a[N]==0) break;

		for(LL now=N;now!=1;now=father[now])
		{
			flow[father[now]][now]+=a[N];
			flow[now][father[now]]-=a[N];
		}

		ans+=a[N];
	}

	printf("%I64d\n",ans);
	/*
	long long sum=0;
	for(int i=1;i<=N;i++) sum+=flow[i][N];
	printf("%I64d\n",sum);
	*/
}

int main()
{
	freopen("ditch.in","r",stdin);
	freopen("ditch2.out","w",stdout);

	while(scanf("%I64d%I64d",&M,&N)==2)////N个节点M条边
	{
		readdata();
		EK();
	}
	return 0;
}

距离标记最短增广路算法(SAP)

其实大致思路就是每次增广最短路径, 维护一个dis数组表示每个点到汇点的最小跳数,dis数组必须有这个性质: dis[i]<= dis[j] + 1 ( r[i][j] > 0);定义一条允许弧:如果满足:dis[i] = dis[j] + 1( r[i][j] >0);那么连接i,j的边就叫允许弧, 书上有这个定理:从源点到汇点的最短路一定是用允许弧构成。所以每次扩展路径都找允许弧,如果i没有允许弧就更新dis[i] = min{ dis[j] + 1 | r[i][j]> 0); 如此容易知道当dis[source] = n是就增广完毕,因为从源点到汇点最多有n-1步。最后dis数组可以用一个BFS初始化,但是实际操作中dis往往被初始化为0。

//sap gap优化版
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;

#define INF 0x3f3f3f3f

typedef long long LL;

long long vd[210],dis[210],flow[210][210],F[210][210];//dis:标号距离, 数组vd[i]记录标号距离为i的顶点个数,flow为残量网络
long long M,N;

void readdata()
{
	memset(flow,0,sizeof(flow));
	memset(dis,0,sizeof(dis));
	memset(vd,0,sizeof(vd));
	for(int i=1;i<=M;i++)
	{
		int a,b;LL x;
		scanf("%d%d%I64d",&a,&b,&x);
		flow[a][b]+=x;
	}
}

long long dfs(LL now,LL f)//返回now到终点N的增广量
{
	if(now==N) return f;//找到增广路

	LL ans=0;
	for(int i=1;i<=N;i++)
		if(flow[now][i]>0 && dis[now]==dis[i]+1)//是允许弧
		{
			LL tmp=dfs(i,min(f-ans,flow[now][i]));//tmp是下一个点到终点N的增广量
			flow[now][i]-=tmp; flow[i][now]+=tmp;
			ans+=tmp;
			if(ans==f) return ans;
		}
	if(dis[1]>=N) return ans;//如果起点的标号距离大于N,就不存在增广路
	vd[dis[now]]--;
	if(vd[dis[now]]==0) dis[1]=N;//如果标号距离是唯一的,删除该点后出现断层不再有可行弧(gap优化)。以d[1]为标记
	dis[now]++;//向上提一层
	vd[dis[now]]++;
	return ans;
}

void SAP()
{
	vd[0]=N;
	LL ans=0;
	while(dis[1]<N) ans+=dfs(1,INF);
	printf("%I64d\n",ans);
}

int main()
{
	freopen("ditch.in","r",stdin);

	while(scanf("%I64d%I64d",&M,&N)==2)
	{
		readdata();
		SAP();
	}

	while(1);
	return 0;
}	

广搜标号法(dinic)

先广搜标一下等级,相邻等级才能有流量。每次dinic的标号是每次全图标号,然后一次深搜全部增广,然后再残量图标号……比起SAP,dinic每次可以找到多条增广路,效率上来讲,一般会更高效一点

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>

using namespace std;

#define maxN 210
#define INF 0x3f3f3f3f

int N,M,Map[maxN][maxN],Deep[maxN];

void readdata()
{
	memset(Map,0,sizeof(Map));
	for(int i=1;i<=M;i++)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		Map[a][b]+=c;
	}
}

bool bfs()//广搜标号
{
	memset(Deep,0,sizeof(Deep));
	queue<int> q;
	q.push(1); Deep[1]=1;

	while(!q.empty())
	{
		int v=q.front(); q.pop();
		for(int u=1;u<=N;u++)
			if(!Deep[u] && Map[v][u]>0)
			{
				q.push(u);
				Deep[u]=Deep[v]+1;
				if(u==N) return 1;
			}
	}

	return 0;
}

int dfs(int v,int Flow)//进行一次增广
{
	if(v==N || Flow==0) return Flow;
	int Cap=Flow;//Cap指当前剩余量
	for(int u=1;u<=N;u++)
		if(Deep[v]+1==Deep[u] && Map[v][u]>0)
		{
			int x=dfs(u,min(Map[v][u],Cap));
			Cap-=x;
			Map[v][u]-=x; Map[u][v]+=x;
			if(Cap==0) return Flow;
		}
	return Flow-Cap;
}

int dinic()
{
	int sum=0;
	while(bfs()) sum+=dfs(1,INF);//每次对残量网络进行广搜标号和增广,直到不能广搜标号为止
	return sum;
}

int main()
{
	freopen("ditch.in","r",stdin);

	while(scanf("%d%d",&M,&N)==2)
	{
		readdata();
		printf("%d\n",dinic());
	}
	
	while(1);
	return 0;
}









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值