上下界网络流学习

上下界网络流

其实之前也写过博客介绍过的,但还没有完整地讲过一遍,这里就来总结一下。

无源汇有上下界可行流

针对任何的有上下界的网络流的边,我们考虑将它转化一下,将流量为 l i / r i l_{i}/r_{i} li/ri的,边拆成 l i l_{i} li r i − l i r_{i}-l_{i} rili
原边转化后
对于流量为 l i l_{i} li的边,我们称其为必要边,即这条边必须流满,而另一条流量为 r i − l i r_{i}-l_{i} rili的边,就是不需要流满的非必要边。
我们考虑如何让必要边一定流满。
我们可以新建一个超级源点 S S S与一个超级汇点 T T T。我们将 u u u连向 T T T S S S连向 v v v,它们的流量都是 l i l_{i} li
这样的话,当原图是合法时并没有改变 u u u v v v两个点自己的经过流量,只会影响我们新建的 S S S T T T
在这里插入图片描述

当将所有的边都进行这样的转化后,我们只需要跑一边dinic,看原点的流量流满没有即可。
至于每条非必需边的流量,只需要看它的非必需边在这个过程中减少了多少即可。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 205
#define MAXM 30005
#define reg register
typedef long long LL;
typedef pair<int,LL> pii;
const int INF=0x7f7f7f7f;
const LL inf=0x7f7f7f7f7f7f;
template<typename _T>
inline void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,m,val[MAXN],head[MAXN],tot,S,T,cnt,sum,dep[MAXN],id[MAXM],dy[MAXM];
queue<int> q;
struct edge{int to,nxt,flow,op;}e[MAXM];
void addEdge(int u,int v,int w){e[++tot]=(edge){v,head[u],w};head[u]=tot;}
void addedge(int u,int v,int w){addEdge(u,v,w);e[tot].op=tot+1;addEdge(v,u,0);e[tot].op=tot-1;}
bool bfs(){
	while(!q.empty())q.pop();
	for(int i=1;i<=cnt;i++)dep[i]=0;q.push(S);dep[S]=1;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(!dep[v]&&e[i].flow){
				q.push(v);dep[v]=dep[u]+1;
				if(v==T)return 1;
			}
		}
	}
	return 0;
}
int dosaka(int u,int maxf){
	if(u==T||!maxf)return maxf;int res=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(!e[i].flow||dep[v]!=dep[u]+1)continue;
		int f=dosaka(v,min(e[i].flow,maxf));
		e[i].flow-=f;e[e[i].op].flow+=f;res+=f;maxf-=f;
	}
	return res;
}
LL sakura(){LL res=0;while(bfs())res+=1ll*dosaka(S,INF);return res;}
signed main(){
	read(n);read(m);S=n+1;cnt=T=n+2;
	for(int i=1;i<=m;i++){
		int s,t,low,up;read(s);read(t);read(low);read(up);
		dy[i]=low;addedge(s,t,up-low);id[i]=tot;val[s]-=low;val[t]+=low;
	}
	for(int i=1;i<=n;i++){
		if(val[i]<0)addedge(i,T,-val[i]);
		if(val[i]>0)addedge(S,i,val[i]),sum+=val[i];
	}
	int ans=sakura();if(ans==sum)puts("YES");else{puts("NO");return 0;}
	for(int i=1;i<=m;i++)printf("%d\n",e[id[i]].flow+dy[i]);
	return 0;
}

有源汇有上下界最大流

其实这道题的模型是与上一道题差不多的。
我们先需要判断这个图是否可行,还是向上题一样建图跑网络流,唯一不同的是需要在原来的汇点 t t t和原点 s s s之间连一条流量为inf的边,再跑网络流来判断。
因为此时的流量是守恒的,这条边的流量就等于可行流的流量。

如果要求出它的最大流的话,就只需要将源点汇点改为原来的 s s s t t t,再跑一道最大流即可。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 305
#define MAXM 40005
#define reg register
typedef long long LL;
typedef pair<int,LL> pii;
const int INF=0x7f7f7f7f;
const LL inf=0x7f7f7f7f7f7f;
template<typename _T>
inline void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,m,val[MAXN],head[MAXN],tot,s,t,S,T,cnt,sum,dep[MAXN],cur[MAXN];
queue<int> q;
struct edge{int to,nxt,flow,op;}e[MAXM];
struct ming{int u,v,up,down;}a[MAXM];
void addEdge(int u,int v,int w){e[++tot]=(edge){v,head[u],w};head[u]=tot;}
void addedge(int u,int v,int w){addEdge(u,v,w);e[tot].op=tot+1;addEdge(v,u,0);e[tot].op=tot-1;}
bool bfs(){
	for(int i=1;i<=cnt;i++)dep[i]=0,cur[i]=head[i];
	while(!q.empty())q.pop();q.push(S);dep[S]=1;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(!dep[v]&&e[i].flow){
				q.push(v);dep[v]=dep[u]+1;
				if(v==T)return 1;
			}
		}
	}
	return 0;
}
int dosaka(int u,int maxf){
	if(u==T||!maxf)return maxf;int res=0;
	for(int i=cur[u];i;i=e[i].nxt){
		cur[u]=i;int v=e[i].to;if(!e[i].flow||dep[v]!=dep[u]+1)continue;
		int f=dosaka(v,min(e[i].flow,maxf));
		e[i].flow-=f;e[e[i].op].flow+=f;res+=f;maxf-=f;
	}
	return res;
}
LL sakura(){LL res=0;while(bfs())res+=1ll*dosaka(S,INF);return res;}
signed main(){
	read(n);read(m);read(s);read(t);S=n+1;cnt=T=n+2;
	for(int i=1;i<=m;i++){
		read(a[i].u);read(a[i].v);read(a[i].down);read(a[i].up);
		addedge(a[i].u,a[i].v,a[i].up-a[i].down);
		val[a[i].u]-=a[i].down;val[a[i].v]+=a[i].down;
	} 
	for(int i=1;i<=n;i++){
		if(val[i]<0)addedge(i,T,-val[i]);
		if(val[i]>0)addedge(S,i,val[i]),sum+=val[i];
	}
	addedge(t,s,INF);LL ans=sakura();
	if(ans<sum){puts("please go home to sleep");return 0;}
	S=s;T=t;ans=sakura();printf("%lld\n",ans);
	return 0;
}

有源汇有上下界最小流

最小流的话判断过程是与上面的是一样的,唯一不同的就是最小流的求法。
很荣容易发现,再最开始跑dinic来判断的过程中, S S S T T T的过程中已经将 s s s t t t的路径上的非必要边都扩展了。
所以我们只需要将 t t t s s s的无限流量的边的流量记录下来,减去删去这条边后由 t t t s s s的最大流即可。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 1000100
#define reg register
typedef long long LL;
typedef pair<int,LL> pii;
const int INF=0x7f7f7f7f;
template<typename _T>
inline void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,m,val[MAXN],head[MAXN],tot,s,t,S,T,cnt,sum,dep[MAXN],cur[MAXN];
queue<int> q;
struct edge{int to,nxt,flow,op;}e[MAXN];
struct ming{int u,v,up,down;}a[MAXN];
inline void addEdge(int u,int v,int w){e[++tot]=(edge){v,head[u],w};head[u]=tot;}
inline void addedge(int u,int v,int w){addEdge(u,v,w);e[tot].op=tot+1;addEdge(v,u,0);e[tot].op=tot-1;}
inline bool bfs(){
	for(reg int i=1;i<=cnt;++i)dep[i]=0,cur[i]=head[i];
	while(!q.empty())q.pop();q.push(S);dep[S]=1;
	while(!q.empty()){
		const int u=q.front();q.pop();
		for(reg int i=head[u];i;i=e[i].nxt){
			const int v=e[i].to;
			if(!dep[v]&&e[i].flow)
				q.push(v),dep[v]=dep[u]+1;
		}
	}
	return (dep[T]>0);
}
int dosaka(const int u,int maxf){
	if(u==T||!maxf)return maxf;int res=0;
	for(reg int &i=cur[u];i;i=e[i].nxt){
		int v=e[i].to;if(!e[i].flow||dep[v]!=dep[u]+1)continue;
		int f=dosaka(v,min(e[i].flow,maxf));
		e[i].flow-=f;e[e[i].op].flow+=f;
		res+=f;maxf-=f;if(!maxf)break;
	}
	return res;
}
int sakura(){int res=0;while(bfs())res+=1ll*dosaka(S,INF);return res;}
signed main(){
	read(n);read(m);read(s);read(t);S=n+1;cnt=T=n+2;
	for(reg int i=1;i<=m;++i){
		read(a[i].u);read(a[i].v);read(a[i].down);read(a[i].up);
		addedge(a[i].u,a[i].v,a[i].up-a[i].down);
		val[a[i].u]-=a[i].down;val[a[i].v]+=a[i].down;
	} 
	for(reg int i=1;i<=n;++i){
		if(val[i]<0)addEdge(i,T,-val[i]);
		if(val[i]>0)addEdge(S,i,val[i]),sum+=val[i];
	}
	addedge(t,s,INF);int tmp=sakura(),ans=e[tot].flow;e[tot-1].flow=e[tot].flow=0;
	for(int i=head[S];i;i=e[i].nxt)if(e[i].flow){puts("please go home to sleep");return 0;}
	S=t;T=s;ans=ans-sakura();printf("%lld\n",ans);
	return 0;
}

LOJ上恰好这三道题都有,就先将这三道题的链接与代码贴出来了。

谢谢!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值