【网络流】无源汇有上下界可行流

【概述】
网络流是一种很神奇的算法,学会了就能打很多暴力(网络流的一些高端操作如线性规划我是不可能会的) 。一般的网络流只限制了边流量的上界,这里介绍一种变形,限制了边流量上下界的,判断是否存在可行流(循环流)。
【算法描述】
我们显然只能借助于最大流算法。下界很讨厌,我们很难在最大流算法中满足对下界的限制。所以我们一开始就让所有边有下界大小的流量。但是,这样某些点的流量显然是不平衡的。为了让这些点达到平衡,我们新建(就是这么简单粗暴 )源点和汇点,让这些点向源点和汇点连边,表示这些点需要源点提供的流量或这些点能向汇点提供的流量。显然,我们接下来需要处理掉新建的点对原图的影响。当且仅当这些新加的边满流时,原图中流量才可以平衡。为了让这些边满流(依然简单粗暴 ),我们从源点开始跑一次最大流即可。因为我们想让源点的出边流量尽量的大,这不就是求最大流吗?如果最后这些新加的边没有满流,那么原图中不存在可行流。
【算法步骤】

  1. 构造初始流:让所有边有下界的流量,这样可能流量不平衡,不过没关系。
  2. 将流量不平衡的点向新建的源点或汇点连边,边权表示满足当前情况仍然需要或供大于求的流量。
  3. 构造附加流:以上界减下界为新边权,从源点出发跑一次最大流。如果源点相连的边都满流,则原问题有解,否则无解。

(其它实现细节见代码,代码虽然不算美观,但是可读)

代码:

#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1e5+5;
int n,m;
inline int red()
{
    int data=0;int w=1; char ch=0;
    ch=getchar();
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
    return data*w;
}
struct node{
	int u,v,w;
	int id;
}e[N<<1|1];
int cnt=1,nxp[N<<1|1],f[N],down[N],up[N],s,t,u[N],v[N];
inline void add(int u,int v,int w,int id)
{
	e[++cnt].u=u;e[cnt].v=v;e[cnt].w=w;e[cnt].id=0;
	nxp[cnt]=f[u];f[u]=cnt;
	e[++cnt].u=v;e[cnt].v=u;e[cnt].w=0;e[cnt].id=id;
	nxp[cnt]=f[v];f[v]=cnt;
}
int ans[N];
int d[N];
int dis[N],cur[N];
inline bool bfs()
{
	queue<int>q;
	q.push(s);
	memset(dis,-1,sizeof(dis));
	dis[s]=0;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int re i=f[u];i;i=nxp[i])
		{
			int v=e[i].v;
			if(e[i].w>0&&dis[v]==-1)
			{
				dis[v]=dis[u]+1;q.push(v);
				if(v==t)return 1;
			}
		}
	}
	return 0;
} 
int dfs(int x,int fff)
{	
	if(x==t||fff==0)return fff;
	int used=0;
	for(int &i=cur[x];i;i=nxp[i])
	{
		int v=e[i].v;
		if(e[i].w&&dis[v]==dis[x]+1)
		{
			int w=dfs(v,min(fff,e[i].w));
			if(!w)continue;
			used+=w;fff-=w;
			e[i].w-=w;e[i^1].w+=w;
			if(!fff)break;
		}
	}
	if(!used)dis[x]=-1;
	return used;
}
int main()
{
	n=red();m=red();s=0;t=n+1;
	for(int re i=1;i<=m;i++)
	{
		u[i]=red();v[i]=red();down[i]=red(),up[i]=red();
		d[u[i]]-=down[i];d[v[i]]+=down[i];ans[i]+=down[i];
	}
	for(int re i=1;i<=n;i++)
	{
		if(d[i]>0)add(s,i,d[i],0);
		if(d[i]<0)add(i,t,-d[i],0);
	}
	for(int re i=1;i<=m;i++)
		if(up[i]>down[i])add(u[i],v[i],up[i]-down[i],i);
	while(bfs())
	{
		for(int re i=0;i<=n+1;i++)cur[i]=f[i];
		dfs(s,2e9);
	}
	for(int re i=f[s];i;i=nxp[i])
		if(e[i].w>0)return puts("NO"),0;
	for(int re i=1;i<=cnt;i++)
		ans[e[i].id]+=e[i].w;
	puts("YES");
	for(int re i=1;i<=m;i++)
		printf("%d\n",ans[i]);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值