较优的网络流算法

下面介绍两种时间复杂度较为优秀的网络流算法:dinic和SAP。

dinic

dinic主要是以下两大优化:

  1. 分层

  2. 当前弧优化

分层

每一次dfs之前我们都从源点bfs一次,bfs只走还剩流量可流的边,通过bfs我们把残量网络分层。设i的深度为deep[i],那么在dfs是我们要保证deep[y]=deep[x]+1才走x-y这条边。

当然我们也可以多次dfs再一次bfs,这也不影响正确性。

当前弧优化

这个其实就是记录一下每一个点当前增广到那一条边了,下一次直接从这条边开始增广即可。注意每次改变deep时都要把所有的当前弧清零,因为可用的边发生了变化。

这个优化看似没有多大用,但是实际上可以大大提高效率。

SAP

sap其实就是dinic的进化版。在dinic时我们需要多次bfs来求deep,而在SAP时我们不妨在dfs中实时更新deep。

SAP的核心思路是:当一个点无法增广时,我们要更新它的deep使得它又有可能增广。

往上说的操作是把deep改为连出去的儿子的deep的min-1,但是我身边的同学打的都是把deep全部初始赋为0,然后每次+1(或者把deep全部赋为n,然后每次-1)。

当一种deep的个数变为0的时候,这就标志着增广完毕了。因为如果一种deep的个数变为0,那么就说明deep图出现了断层。在这种情况下是不可能增广成功的。

最后要注意的是,每次修改一个一个点的deep时,都要把它的当前弧优化的数组清零。

下面贴一下代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define MAXN 1010
#define MAXM 1000010

struct map
{
	int x;
	int y;
	int l;
};
map way[MAXM];
int first[MAXN],nxt[MAXM],num[MAXN],deep[MAXN],cur[MAXN],pre[MAXN],n,m,ans,tf=1,w;
int dfs(int z)
{
	int i;
	if(tf==0)return 0;
	if(z==n)
	{
		w=-1;
		i=pre[z];
		while(i>=1&&i<=m)
		{
			if(way[i].l<w||w==-1)w=way[i].l;
			i=pre[way[i].x];
		}
		ans+=w;
		i=pre[z];
		while(i>=1&&i<=m)
		{
			way[i].l-=w;
			if(i<=m/2)way[i+m/2].l+=w;
			else way[i-m/2].l+=w;
			i=pre[way[i].x];
		}
		return 100;
	}
	else
	{
		while(cur[z]>=1&&cur[z]<=m)
		{
			i=cur[z];
			if(way[i].l>0&&deep[way[i].y]==deep[z]+1)
			{
				pre[way[i].y]=i;
				if(dfs(way[i].y)==100)return 100;
			}
			cur[z]=nxt[cur[z]];
		}
		num[deep[z]]--;if(num[deep[z]]==0){tf=0;}
		deep[z]--;num[deep[z]]++;
		cur[z]=first[z];
	}
	return 50;
}
int main()
{
int i,j;
scanf("%d %d",&m,&n);
for(i=1;i<=m;i++)
{
	scanf("%d %d %d",&way[i].x,&way[i].y,&way[i].l);
	way[i+m].x=way[i].y;way[i+m].y=way[i].x;way[i+m].l=0;
}
m*=2;for(i=m;i>=1;i--)nxt[i]=first[way[i].x],first[way[i].x]=i;
for(i=1;i<=n;i++)deep[i]=n;
for(i=1;i<=n;i++)num[deep[i]]++;
for(i=1;i<=n;i++)cur[i]=first[i];
while(true)
{
	dfs(1);
	if(tf==0)break;
}
printf("%d",ans);
}
发布了149 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/chiyankuan/article/details/99586747
今日推荐