费用流处理负圈的方法

以最小费用最大流为例,我们可以通过退流来消除费用为负数的边。
具体过程:
不妨设有一条从\(u\)\(v\)的容量为\(c\)费用为\(d\)的边(\(d<0\))。
先强制满流,把答案加上\(c\times{d}\)
之后,从\(u\)\(T\)\(S\)\(v\)各连一条容量为\(c\),费用为0的边,用来调整流量。这两条边要使用手段强制满流。
最后,连一条从\(v\)\(u\)的容量为\(c\)费用为\(-d\)的边,用于退流。
这样就没有负环了。

也有解决负环的算法:

此写法过慢,待修改

正常,我们的最小费用流的费用都应该是正数。
但是,有时,边权会出现负数,进而可能出现负圈。
这时,我们可以这样处理:
在每次增广时,先在整张图中找负圈,若能找到,则沿着负圈增广。
否则,再找增广路进行增广。
找负圈使用dfs的spfa会更好写,更快。
可以优化:

  1. 每个点只搜索一次(见注释)。
  2. 如果搜不到负环了,就不需要再考虑负环了。

代码:

bool dfs(int u)
{
	bk[u]=true;
	for(int i=fr[u];i!=-1;i=ne[i])
	{
		if(w[i]>0&&jl[u]+fy[i]<jl[v[i]])
		{
			jl[v[i]]=jl[u]+fy[i];
			la[v[i]]=u;lb[v[i]]=i;
			if(bk[v[i]])
			{
				fq=v[i];
				return true;
			}
			if(dfs(v[i]))
				return true;
		}
	}
	bk[u]=false;
	return false;
}
bool spfa()
{
	for(int i=1;i<=N;i++)
	{
		bk[i]=false;
		jl[i]=inf;
	}
	for(int i=1;i<=N;i++)
	{
		fq=-1;
		if(jl[i]==inf&&dfs(i))//每个点只搜索一次
			return true;
	}
	for(int i=1;i<=N;i++)
	{
		bk[i]=false;
		jl[i]=inf;
	}
	jl[S]=0;fq=-1;
	dfs(S);
	return jl[T]<inf;
}
int feiyl(int &fei)
{
	int he=0;fei=0;
	while(spfa())
	{
		if(fq==-1)
		{
			int u=T,zx=inf;
			while(u!=S)
			{
				if(w[lb[u]]<zx)
					zx=w[lb[u]];
				u=la[u];
			}
			he+=zx;fei+=jl[T]*zx;u=T;
			while(u!=S)
			{
				w[lb[u]]-=zx;
				w[lb[u]^1]+=zx;
				u=la[u];
			}
		}
		else
		{
			int u=fq,zx=inf;
			while(1)
			{
				if(w[lb[u]]<zx)
					zx=w[lb[u]];
				u=la[u];
				if(u==fq)break;
			}u=fq;
			while(1)
			{
				w[lb[u]]-=zx;
				w[lb[u]^1]+=zx;
				fei+=zx*fy[lb[u]];
				u=la[u];
				if(u==fq)break;
			}
		}
	}
	return he;
}
posted @ 2020-02-18 20:08  lnzwz  阅读(1415)  评论(0编辑  收藏  举报