loj #115. 无源汇有上下界可行流

题目链接:https://loj.ac/problem/115


参考博客:http://www.cnblogs.com/liu-runda/p/6262832.html


模型:一个网络,求出一个流,使得每条边的流量必须>=Li且<=Hi,每个点必须满足总流入量=总流出量(流量守恒)(这个流的特点是循环往复,无始无终).

这个算法是有上下界网络流算法的基础,只要深刻理解这个算法其他算法也就水到渠成,因此我用大篇幅力图将这个算法的思想和细节阐述清楚.

可行流算法的核心是将一个不满足流量守恒的初始流调整成满足流量守恒的流.

流量守恒,即每个点的总流入量=总流出量

如果存在一个可行流,那么一定满足每条边的流量都大于等于流量的下限.因此我们可以令每条边的流量等于流量下限,得到一个初始流,然后建出这个流的残量网络.(即:每条边的流量等于这条边的流量上限与流量下限之差)这个初始流不一定满足流量守恒,因此最终的可行流一定是在这个初始流的基础上增大了一些边的流量使得所有点满足流量守恒.

因此我们考虑在残量网络上求出一个另不满足流量守恒的附加流,使得这个附加流和我们的初始流合并之后满足流量守恒.即:

如果某个点在所有边流量等于下界的初始流中满足流量守恒,那么这个点在附加流中也满足流量守恒,

如果某个点在初始流中的流入量比流出量多x,那么这个点在附加流中的流出量比流入量多x.

如果某个点在初始流中的流入量比流出量少x,那么这个点在附加流中的流出量比流入量少x.

         可以认为附加流中一条从u到v的边上的一个流量代表将原图中u到v的流量增大1

X的数值可以枚举x的所有连边求出.比较方便的写法是开一个数组A[],A[i]表示i在初始流中的流入量-流出量的值,那么A[i]的正负表示流入量和流出量的大小关系,下面就用A[i]表示初始流中i的流入量-流出量

但是dinic算法能够求的是满足流量守恒的有源汇最大流,不能在原网络上直接求一个这样的无源汇且不满足流量守恒的附加流.注意到附加流是在原网络上不满足流量守恒的,这启发我们添加一些原网络之外的边和点,用这些边和点实现”原网络上流量不守恒”的限制.

具体地,如果一个点i在原网络上的附加流中需要满足流入量>流出量(初始流中流入量<流出量,A[i]<0),那么我们需要给多的流入量找一个去处,因此我们建一条从i出发流量=-A[i]的边.如果A[i]>0,也就是我们需要让附加流中的流出量>流入量,我们需要让多的流出量有一个来路,因此我们建一条指向i的流量=A[i]的边.

当然,我们所新建的从i出发的边也要有个去处,指向i的边也要有个来路,因此我们新建一个虚拟源点ss和一个虚拟汇点tt(双写字母是为了和有源汇网络流中的源点s汇点t相区分).新建的指向i的边都从ss出发,从i出发的边都指向tt.一个点要么有一条边指向tt,要么有一条边来自ss,

指向tt的边的总流量上限一定等于ss流出的边的总流量上限,因为每一条边对两个点的A[i]贡献一正一负大小相等,所以全部点的A[i]之和等于0,即小于0的A[i]之和的绝对值=大于0的A[i]之和的绝对值.

如果我们能找到一个流满足新加的边都满流,这个流在原图上的部分就是我们需要的附加流(根据我们的建图方式,“新加的边都满流”和”附加流合并上初始流得到流量平衡的流”是等价的约束条件).

那么怎样找出一个新加的边都满流的流呢?可以发现假如存在这样的方案,这样的流一定是我们所建出的图的ss-tt最大流,所以跑ss到tt的最大流即可.如果最大流的大小等于ss出发的所有边的流量上限之和(此时指向tt的边也一定满流,因为这两部分边的流量上限之和相等).

最后,每条边在可行流中的流量=容量下界+附加流中它的流量(即跑完dinic之后所加反向边的权值).


代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace DINIC
{
	const int MAXN=500+10;
	const int MAXM=4e5+10;
	const int INF=0x3f3f3f3f;
	struct Edge
	{
		int u,v,cap,nxt;
		Edge(){}
		Edge(int _u,int _v,int _cap,int _nxt):u(_u),v(_v),cap(_cap),nxt(_nxt){}
	}E[MAXM];
	int head[MAXN],dis[MAXN],vis[MAXN];
	int tol;
	void init()
	{
		tol=0;
		memset(head,-1,sizeof(head));
	}
	void addedge(int u,int v,int cap)
	{
		E[tol]=Edge(u,v,cap,head[u]);//正向边
		head[u]=tol++;
		E[tol]=Edge(v,u,0,head[v]);//反向边容量为0
		head[v]=tol++;
	}
	bool BFS(int S,int T)
	{
		queue<int> q;
		q.push(S);
		memset(dis,0x3f,sizeof(dis));
		dis[S]=0;
		while(!q.empty())
		{
			int x=q.front();
			q.pop();
			for(int i=head[x];~i;i=E[i].nxt)
			{
				if(E[i].cap>0&&dis[E[i].v]==INF)
				{
					dis[E[i].v]=dis[x]+1;
					if(E[i].v==T)
                    	return true;
					q.push(E[i].v);
				}
			}
		}
		return dis[T]<INF; //返回是否能够到达汇点
	}
	int dfs(int x,int maxflow,int T)
	{
		if(x==T||maxflow<=0)
			return maxflow;
		//i=vis[x]当前弧优化
		int ret=0;
		for(int &i=vis[x];~i;i=E[i].nxt)
		{
			if(dis[E[i].v]==dis[x]+1&&E[i].cap>0)
			{
				int flow=dfs(E[i].v,min(maxflow,E[i].cap),T);
				if(flow)
				{
					ret+=flow;
            		maxflow-=flow;
					E[i].cap-=flow;//正向边流量降低
					E[i^1].cap+=flow; //反向边流量增加
				}
				if(maxflow==0)
					break;
			}
		}
		return ret;//找不到增广路退出
	}
	ll dinic(int S,int T,int N)
	{
		ll ans=0;
		while(BFS(S,T))//建立分层图
		{
			int flow;
			for(int i=0;i<=N;i++)//初始化vis
			{
				vis[i]=head[i];
			}
			while(flow=dfs(S,INF,T))//一次BFS可以进行多次增广
				ans+=(ll)flow;
		}
		return ans;
	}
}
using namespace DINIC;
int a[MAXN],low[MAXM];
int n,m;
bool judge()
{
	for(int i=head[0];~i;i=E[i].nxt)
	{
		if(E[i].cap!=0)
			return false;
	}
	for(int i=head[n+1];~i;i=E[i].nxt)
	{
		if(E[i^1].cap!=0)
			return false;
	}
	return true;
}
void solve()
{
	int cnt;
	memset(a,0,sizeof(a));
	init();
	for(int i=1;i<=m;i++)
	{
		int u,v,up;
		scanf("%d%d%d%d",&u,&v,&low[i],&up);
		a[v]+=low[i];a[u]-=low[i];
		addedge(u,v,up-low[i]);
	}
	cnt=tol;
	for(int i=1;i<=n;i++)
	{
		if(a[i]>0)
			addedge(0,i,a[i]);
		if(a[i]<0)
			addedge(i,n+1,-a[i]);
	}
	dinic(0,n+1,n+2);
	if(!judge())
	{
		puts("NO");
		return ;
	}
	puts("YES");
	for(int i=0;i<cnt;i+=2)
	{
		printf("%d\n",E[i^1].cap+low[i/2+1]);
	}
}
int main()
{
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	while(~scanf("%d%d",&n,&m))
	{
		solve();
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值