一辈子都学不会的有上下界的网络流

抄袭来源

zyf学姐的blog

成爷的blog

这个标题好潮啊,大概是二轮前学的最后一点新东西了吧

1.无源汇可行流

对于普通的网络流图,每一条边都有一个流量上界,一个源点,一个汇点,之后我们就可以在上面跑跑最大流,算算费用什么的

但是现在有这样一张图,对于每一条边\((u,v)\),有一个流量下界\(b(u,v)\)和一个流量上界\(c(u,v)\),要求每一条边的实际流量\(f(u,v)\)满足\(b(u,v)\leq f(u,v)\leq c(u,v)\),同时还得是一张网络流图,就是对于每一个节点\(i\)满足\(\sum f(u,i)=\sum f(v,i)\),也就是流量平衡

同时这样图由于没有源汇,所以所有流量都在这张图里循环流动

有一个直观的想法,我们先将这张图转化成一张普通的网络流图,即每一条边下界为\(0\),上界为\(c(u,v)-b(u,v)\),这样我们求一个普通的网络流,最后再给每一条边加上下界\(b(u,v)\),这样我们就能保证每条边的流量在\([b(u,v),c(u,v)]\)之间了

但是我们注意到一个非常显然的问题,我们这样并没有保证每个点在加上所有的边的流量下界的时候流量平衡,所以现在就需要发挥网络流调整的特性了

我们令\(d_i\)等于点\(i\)的流入这个点的所有边的流量下界之和减掉流出这个点的所有边的下界之和

我们分情况讨论一下

  • 如果\(d_i=0\),那么对于这个点来说加上所有流量下界之后仍然是平衡的,于是我们不用管它

  • 如果\(d_i>0\),如果我们不管这个点,那么就会出现\(\sum f(u,i)-\sum f(i,v)=d_i\),为了让后面的\(\sum f(i,v)\)大一些,我们设置一个超级源点\(S\)\(S\)向点\(i\)连一条流量为\(d_i\)的边,这样为了把这\(d_i\)的流量流走,后面的\(\sum f(i,v)\)就需要增加\(d_i\),从而使得流量平衡

  • 如果\(d_i<0\),我们不管这个点的话,那么就会出现\(\sum f(u,i)-\sum f(i,v)=-d_i\),为了让前面的\(\sum f(u,i)\)大一些,我们设置一个超级汇点\(T\),让\(i\)\(T\)连一条流量为\(-d_i\)的边,这样为了能让这些流量留到汇点,前面的\(\sum f(u,v)\)势必要增加\(d_i\),从而使得流量平衡

当然能把这\(d_i\)的流量流过来,必须是要在满流的情况下行的,于是我们判断一下加入\(S,T\)后这张图是否满流,如果满流,那么每条边的实际流量就等于\(b(u,v)\)加上在这张图上的流量;否则说明不存在一个可行流

loj的板子题

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
inline int read() {
	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
const int inf=999999999;
const int maxn=205;
std::queue<int> q;
struct E{int v,nxt,f;}e[60005];
int n,m,num=1,S,T,tot;
int head[maxn],cur[maxn],d[maxn],r[maxn],id[15000],ans[15000];
inline void C(int x,int y,int f) {e[++num].v=y;e[num].nxt=head[x];head[x]=num;e[num].f=f;}
inline void add(int x,int y,int f) {C(x,y,f),C(y,x,0);}
inline int BFS() {
	for(re int i=S;i<=T;i++) d[i]=0,cur[i]=head[i];
	d[S]=1;q.push(S);
	while(!q.empty()) {
		int k=q.front();q.pop();
		for(re int i=head[k];i;i=e[i].nxt)
		if(!d[e[i].v]&&e[i].f) 
			d[e[i].v]=d[k]+1,q.push(e[i].v);
	}
	return d[T];
}
int dfs(int x,int now) {
	if(x==T||!now) return now;
	int flow=0,ff;
	for(re int& i=cur[x];i;i=e[i].nxt)
	if(d[e[i].v]==d[x]+1) {
		ff=dfs(e[i].v,min(e[i].f,now));
		if(ff<=0) continue;
		now-=ff,flow+=ff,e[i].f-=ff,e[i^1].f+=ff;
		if(!now) break;
	}
	return flow;
}
int main() {
	n=read(),m=read();T=n+1;
	for(re int v,x,y,i=1;i<=m;i++) {
		x=read(),y=read(),ans[i]=read();
		v=read();add(x,y,v-ans[i]);id[i]=num;
		r[x]-=ans[i],r[y]+=ans[i];
	}
	for(re int i=1;i<=n;i++)
	if(r[i]>0) add(S,i,r[i]),tot+=r[i];	
		else add(i,T,-r[i]);
	while(BFS()) tot-=dfs(S,inf);
	if(tot) return puts("NO"),0;
	puts("YES");
	for(re int i=1;i<=m;i++) printf("%d\n",ans[i]+e[id[i]].f); 
	return 0;
}

2.有源汇可行流

和上面的区别就是多了两个特殊的点,一个源点\(ss\)只流出不流入,一个汇点\(tt\)只流入不流出

但是注意到源点流出的流量等于汇点流入的流量,于是我们可以考虑加入一条边\([0,\infty]\)\(tt\)指向\(ss\),这样从汇点的流量又回到了源点,从而变成了一个循环流

于是和上面的无源汇一样了

3.有源汇最大流

我们先做一遍有源汇可行流再说

这个时候我们求出来了一个可行流,流量就是\(tt\)\(ss\)那条边的流量

这样我们只是求了一个可行流,我们还需要让这个流量尽量大

于是我们去掉\(tt\)\(ss\)的那一条边,我们再从\(ss\)\(tt\)求一个最大流,和上面的可行流加起来就是答案了

loj的ac链接

4.有源汇最小流

还是先求出一个可行流再说

我们现在需要让这个可行流尽量小,我们希望把这个流量减掉一个尽量大的数

减掉流量,于是我们退流!

还是去掉\(tt\)\(ss\)这条边,之后我们从\(tt\)\(ss\)退流,这样退掉尽量多的流量就可以使得可行流尽量小

loj的ac链接

5.有源汇最小费用可行流

其实和有源汇可行流的建图方式一模一样

放个板子题的题解

posted @ 2019-04-26 08:01  asuldb  阅读(153)  评论(0编辑  收藏  举报