0820-有上下界的可行流-讲解+模板LOJ115,116,117

版权声明:虽然我只是个小蒟蒻但转载也请注明出处哦 https://blog.csdn.net/weixin_42557561/article/details/81874052

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

传送门

首先定义 f (u,i)是从u到 i 的流量,b()是这条边的下限,c()是这条边的上限,g() 是可增加的部分。

我们将每一条边的流量分为两个部分,f(u,i) = b(u,i) + g(u,i),那么显然 c(u,i )>=b( u , i)+g (u,i )【*】

又根据流量平衡定理 sigma 【 b(u,i) + g(u,i)】=sigma  【b(i,v) + g(i,v)】

再移项得到 sigma  b(u,i) - sigma  b(i,v) =sigma g(i,v) - sigma g(u,i)

左边的是一个定值,我们看做 m(i)

则 m(i)= sigma g(i,v) - sigma g(u,i)

若m(i)> 0 则说明 u ~ i 这条边的下限比 i ~  v 的下限要大,那么u i 可以松弛的 g (u,i)就应该小于 i v 可以松弛的 g (i,v),这样流量才可能平衡咯

基于这条性质,我们建立一个超级源点 ss,超级汇点 tt,对于每一个点而言我们计算它的m(i),若大于0就从 ss 往 i 建一条边权为m(i)的边,若小于0就从 i 往 tt 建一条边权为 - m(i)的边

原来各个点之间的边值就相当于可以松弛的g (u,i),那么根据【*】这个式子可知, g 最多可以取到 c(u,i )- b( u , i),所以两点之间的容量(可以流过的最大值)=c(u,i )- b( u , i),然后在 ss 到 tt 之间跑一个最大流,如果从 ss 出来的每一条边都满流了,说明满足了所有下限,我们就算找到一条可行流(也可以判断最后跑出来的maxflow是否等于sigma m(i)【只取m(i)>0的情况】,我的代码就是这样的)

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<iostream>
#include<queue>
#define N 205
#define M 24000
#define inf 0x7fffffff
#define in read()//为了让代码好看一点我把读优省略了
#define debug(x) cerr<<#x<<'='<<x<<endl//这个东西很玄妙我在文章的最后讲一下
using namespace std;
int n,m,ss,tt;
int nxt[M],head[N],to[M],cap[M],cnt=1,end[N];
int cur[M],lev[M],low[M];
void add(int x,int y,int l,int r){
	nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;cap[cnt]=r-l;//建的是可松弛的边
	low[cnt]=l;//记录下每一条边的下限 
	nxt[++cnt]=head[y];head[y]=cnt;to[cnt]=x;cap[cnt]=0;
	end[y]+=l;end[x]-=l;//因为最后是sigma  b(u,i) - sigma  b(i,v)这样的,我们就直接在以 i 结尾的时候加上l,以 i 开始的时候减去 l ,就等价于这个式子了
}
bool bfs(){
	memset(lev,-1,sizeof(lev));
	memcpy(cur,head,sizeof(head));//当前弧优化操作
	queue<int > q;//如果定义在外面则每次都需要清空
	q.push(ss);lev[ss]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int e=head[u];e;e=nxt[e]){
			int v=to[e];
			if(cap[e]>0&&lev[v]==-1){
				lev[v]=lev[u]+1;
				q.push(v);
				if(v==tt) return true;
			}
		}
	}
	return false;
}
int dinic(int u,int flow){
	if(u==tt) return flow;
	int res=0;
	for(int &e=cur[u];e;e=nxt[e]){
		int v=to[e];
		if(cap[e]>0&&lev[v]>lev[u]){
			int delta=dinic(v,min(flow-res,cap[e]));
			if(delta){
				cap[e]-=delta;cap[e^1]+=delta;
				res+=delta;if(res==flow) break;
			}
		}
	}
	if(res<=flow) lev[u]=-1;
	return res;
}
int main(){
	n=in;m=in;
	int i,j,k;
	ss=0;tt=n+1;
	for(i=1;i<=m;++i){
		int s,t,ll,rr;
		s=in;t=in;ll=in;rr=in;
		add(s,t,ll,rr);
	}
	int sum=0;
	for(i=1;i<=n;++i){
		int hh=end[i];
		if(hh<0) add(i,tt,0,-hh);
		else add(ss,i,0,hh),sum+=hh;//判断是否存在可行流就看下限流满没有
	}
	int maxflow=0;
	while(bfs())
		maxflow+=dinic(ss,inf);//从ss到tt跑最大流
	if(maxflow==sum){
		printf("YES\n");
		for(i=2;i<=2*m;i+=2){
			printf("%d\n",low[i]+cap[i+1]);//反向边的流量就是这条边在跑最大流的时候流过去了多少加上下限,就是这条边实际流量
		}
	}
	else printf("NO\n");
	return 0;
}

【有源汇有上下界的最大流】

传送门

刚刚讲了无源汇的情况,那么有源汇怎么办呢?

……………………思考…………………………

好啦,聪明的你应该想到啦,我们直接从 t 到 s 连一条(0,inf) 的边,不就变成无源汇的情况啦

然后我们来想想如何求最大流,由于我们照之前的方法找到的只是一条原图中的可行流,而这个时候是满足所有的下限的,我们就只能在可松弛的边上下手,这不就又很容易了吗,我们去掉超级源汇点,然后在 t 到 s 连了一条(0,inf) 的边的图上再从s 到 t 跑最大流,最后跑出来的maxflow就是答案了

为什么呢?仔细思考当我们第一遍跑完最大流后,可行流的流量全都在(0,inf)这条边的反向边上,然后我们跑第二遍最大流,相当于就把可行流的流量退了回去,退到答案里,再增广其他路得到更大的可能

去掉源汇有两种做法:

  1. 把和源汇相连的边其cap全赋值为0(下一个代码使用方法1)
  2. 用一个vis数组标记(本代码使用方法2)
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<iostream>
#include<queue>
#define N 205
#define M 24000
#define inf 0x7fffffff
#define in read()
#define debug(x) cerr<<#x<<'='<<x<<endl
using namespace std;
int n,m,ss,tt,s,t;
int nxt[M],head[N],to[M],cap[M],cnt=1,end[N];
int cur[M],lev[M],low[M];
bool vis[N];
void add(int x,int y,int l,int r){
	nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;cap[cnt]=r-l;
	low[cnt]=l;//记录下每一条边的下限 
	nxt[++cnt]=head[y];head[y]=cnt;to[cnt]=x;cap[cnt]=0;
	end[y]+=l;end[x]-=l;
}
bool bfs(int hh,int ee){
	memset(lev,-1,sizeof(lev));
	memcpy(cur,head,sizeof(head));
	queue<int > q;////////
	q.push(hh);lev[hh]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int e=head[u];e;e=nxt[e]){
			int v=to[e];
			if(cap[e]>0&&lev[v]==-1&&vis[v]==0){
				lev[v]=lev[u]+1;
				q.push(v);
				if(v==ee) return true;
			}
		}
	}
	return false;
}
int dinic(int u,int flow,int ee){
	if(u==ee) return flow;
	int res=0;
	for(int &e=cur[u];e;e=nxt[e]){
		int v=to[e];
		if(cap[e]>0&&lev[v]>lev[u]&&vis[v]==0){
			int delta=dinic(v,min(flow-res,cap[e]),ee);
			if(delta){
				cap[e]-=delta;cap[e^1]+=delta;
				res+=delta;if(res==flow) break;
			}
		}
	}
	if(res<=flow) lev[u]=-1;
	return res;
}
int read(){
	char ch;
	int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9')
		if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return f==1?res:-res;
}
int main(){
	n=in;m=in;s=in;t=in;
	int i,j,k;
	ss=0;tt=n+1;
	for(i=1;i<=m;++i){
		int u,v,ll,rr;
		u=in;v=in;ll=in;rr=in;
		add(u,v,ll,rr);
	}
	int sum=0;
	for(i=1;i<=n;++i){
		int hh=end[i];
		if(hh<0) add(i,tt,0,-hh);
		else add(ss,i,0,hh),sum+=hh;
	}
	add(t,s,0,inf);//有源汇的别忘了连回去一下 
	int maxflow=0;
	while(bfs(ss,tt))
		maxflow+=dinic(ss,inf,tt);
	if(maxflow==sum){
		maxflow=0;//要为0哦
		vis[ss]=1;vis[tt]=1;
		while(bfs(s,t)) maxflow+=dinic(s,inf,t);
		printf("%d\n",maxflow);
	}
	else printf("please go home to sleep\n");
	return 0;
}

【有源汇有上下界的最小流】

传送门

(emmm……这个第8组数据没卡过去,求大佬帮优化Orz)

哈,过了,只要删掉dinic里面的一条语句  就过了

但思路总归没错,最小流在学习了最大流后就很好理解啦

……………………思考……………………

最大流在最后的时候从 s 到 t 跑一遍最大流,看还能增加多少流量

那最小流就倒着来呗,从 t 到 s 跑一遍最大流,看最多能退回去多少,退完过后不就是最小流的情况了吗?

还需要注意一点最小流最后跑dinic的时候还要删掉 t~s的这条边,为了防止退流时多退导致不合法

这个代码去掉源汇点就用的方法一

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<iostream>
#include<queue>
#define N 50010
#define M 405003
#define inf 0x7fffffff
#define in read()
using namespace std;
int n,m,ss,tt,s,t;
int nxt[M],head[N],to[M],cap[M],cnt=1,end[N];
int cur[M],lev[M];
void add(int x,int y,int z){
	nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;cap[cnt]=z;
	nxt[++cnt]=head[y];head[y]=cnt;to[cnt]=x;cap[cnt]=0;
}
bool bfs(int hh,int ee){
	memset(lev,-1,sizeof(lev));
	memcpy(cur,head,sizeof(head));
	queue<int > q;////////
	q.push(hh);lev[hh]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int e=head[u];e;e=nxt[e]){
			int v=to[e];
			if(cap[e]>0&&lev[v]==-1){
				lev[v]=lev[u]+1;
				if(v==ee) return true;
				q.push(v);
			}
		}
	}
	return false;
}
int dinic(int u,int flow,int ee){
	if(u==ee) return flow;
	int res=0;
	for(int &e=cur[u];e;e=nxt[e]){
		int v=to[e];
		if(cap[e]>0&&lev[v]>lev[u]){
			int delta=dinic(v,min(flow-res,cap[e]),ee);
			if(delta){
				cap[e]-=delta;cap[e^1]+=delta;
				res+=delta;if(res==flow) break;
			}
		}
	}
	//这个不要哦这个是假优化,不要的话时间从1006ms降到46ms if(res<=flow) lev[u]=-1;
	return res;
}
inline int read(){
    int ans=0;
    char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
    return ans;
}
inline void write(int x){//输出优化
    if(x>9)write(x/10);
    putchar((x%10)^48);
}
int main(){
	n=in;m=in;s=in;t=in;
	int i,j,k;
	ss=0;tt=n+1;
	for(i=1;i<=m;++i){
		int u,v,ll,rr;
		u=in;v=in;ll=in;rr=in;
		add(u,v,rr-ll);
		end[v]+=ll;end[u]-=ll;
	}
	int sum=0,first;
	add(t,s,inf);//有源汇的别忘了连回去一下 
	first=cnt-1;
	for(i=1;i<=n;++i){
		if(end[i]<0)
			add(i,tt,-end[i]);
		else if(end[i]>0)
			add(ss,i,end[i]),sum+=end[i];
	}
	int maxflow=0;
	while(bfs(ss,tt))
		maxflow+=dinic(ss,inf,tt);
	if(maxflow==sum){
		maxflow=cap[first^1];
		for(i=first;i<=cnt;++i) cap[i]=0;
		while(bfs(t,s)) maxflow-=dinic(t,inf,s);
		write(maxflow);
	}
	else printf("please go home to sleep\n");
	return 0;
}

 #define debug(x) cerr<<#x<<'='<<x<<endl

这个东西是用来防止傻瓜忘了关调试语句而搞出来的,用了这个宏之后这个输出的数只会显示在显示屏上,不会输出在文件里面(也就是不是影响答案)

相当于如果你写:printf (“%d", x )这个来输出 x 作为调试,你改为debug(x),假使忘了关也不会影响答案,那么如果要关的话也是更容易,直接在宏定义那里注解掉即可

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/81874052
今日推荐