有上下界网络流 学习笔记

一、说在前头

前置知识:(dinic)

普通的网络流,我们只限制了每条路的流量,也就是它的上界,但有些特殊题目,还会恶心地限制一下每条道路流量的下界,今天我们要解决的就是这样的题目。

二、无源汇有上下界可行流

这个算法是所有上下界网络流算法的基础,因此我会重点阐述。首先解释一下标题:可行流是指一个网络流中存在的一条满足除源点汇点之外,所有点的流入量等于流出量(即流量守恒)的流。无源汇,就表明这个图中所有点都必须满足流入量等于流出量,我们只需要找到任意一条可行流即可。

首先,我们先让所有边流量强行达到下界,这样的一个流我们称为初始流,显然,初始流不一定满足流量守恒,所以我们需要通过适当在一些边上增加流量来使它满足流量守恒,新增加的流量合起来也是一条流,我们称为附加流。

附加流首先要满足不会让任意一条边流量超过上界,因此,我们将每条边的最大流量设为上界-下界,同时还要弥补初始流中的问题。对于点(i),令(w_i)表示初始流中(i)的流入量-流出量,我们对(w_i)分情况讨论:

  • (w_i=0),初始流流量守恒,不需要对(i)点操作,让附加流满足流量守恒即可。
  • (w_i>0),附加流在满足(i)点流量守恒之后,还要让它多流出(w_i)的流量,于是从附加流的源点(S)(i)连一条流量为(w_i)的边。
  • (w_i<0),附加流要让(i)点额外流入(-w_i)的流量,于是从(i)向附加流的汇点(T)连一条流量为(-w_i)的边。

为了满足流量守恒,额外连的这些边必须满流,于是在新图上跑最大流,只要流量(=sum{w_i}(w_i>0)=sum{-w_i}(w_i<0))则说明有解,否则无解。注意到第二个等号一定满足,所以只用考虑第一个等号即可。

最后求可行流,则将附加流中每条原图中边的流量加上其下界即可。

模板:loj#115

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=410;
const int M=4e5+10;
const int inf=0x3f3f3f3f;
namespace flow{
	struct node{
		int v,f,nxt;
	}e[M];
	int tot;
	int s,t,cnt=1,cur[N],first[N],vis[N],dis[N];
	inline void add(int u,int v,int f){
		e[++cnt].v=v;e[cnt].f=f;e[cnt].nxt=first[u];first[u]=cnt;
	}
	inline void Add(int u,int v,int f){
		add(u,v,f);add(v,u,0);
	}
	inline bool bfs(){
		fill(vis+1,vis+tot+1,0);
		fill(dis+1,dis+tot+1,0);
		queue<int> q;q.push(s);
		dis[s]=0;vis[s]=1;
		while(!q.empty()){
			int u=q.front();q.pop();
			for(int i=first[u];i;i=e[i].nxt){
				int v=e[i].v;
				if(e[i].f>0&&!vis[v]){
					vis[v]=1;dis[v]=dis[u]+1;
					q.push(v);
				}
			}
		}
		return vis[t];
	}
	inline int dfs(int u,int f){
		if(u==t||!f) return f;
		int used=0;
		for(int &i=cur[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(e[i].f>0&&dis[v]==dis[u]+1){
				int fl=dfs(v,min(f,e[i].f));
				used+=fl;f-=fl;
				e[i].f-=fl;e[i^1].f+=fl;
				if(!f) break;
			}
		}
		if(f) dis[u]=inf;
		return used;
	}
	inline int dinic(int S,int T){
		int ans=0;s=S;t=T;
		while(bfs()){
			memcpy(cur,first,sizeof(first));
			ans+=dfs(s,inf);
		}
		return ans;
	}
	inline void init(){
		fill(first+1,first+tot+1,0);tot=0;
		cnt=1;
	}
}
using flow::tot;
using flow::Add;
using flow::cnt;
int n,m,s,t,S,T,w[N],pos[N],L[M];
int main(){
	scanf("%d%d",&n,&m);
	tot=n;S=++tot;T=++tot;
	for(int i=1;i<=m;++i){
		int u,v,l,r;
		scanf("%d%d%d%d",&u,&v,&l,&r);L[i]=l;
		Add(u,v,r-l);w[u]-=l;w[v]+=l;
	}
	int sum=0;
	for(int i=1;i<=n;++i){
		if(w[i]>0) sum+=w[i],Add(S,i,w[i]);
		if(w[i]<0) Add(i,T,-w[i]);
	}
	if(flow::dinic(S,T)!=sum){
		puts("NO");
		return 0;
	}
	else{
		puts("YES");
		for(int i=1,j=3;i<=m;++i,j+=2)
			printf("%d
",L[i]+flow::e[j].f);
	}
	return 0;
}

三、有源汇有上下界可行流

增加了源点(s)和汇点(t)(注意与上文中的(S)(T)区分),源点和汇点不需要满足流量守恒,源点可以任意流出,汇点可以任意流入。

于是我们直接增加一条从(t)连向(s)的流量上限为(inf),无下限的边,然后按照无源汇的做即可,同时这种情况下,可行流对应的原图流量,可以直接通过(t ightarrow s)这条边的流量得到。

四、有源汇上下界最大流

在可行流的基础上,要求原图流量最大。

我们先根据上一个问题的方法,求解出一条可行流。此时我们一定满足了流量守恒,那么在附加流的残量网络上,我们任意跑出一条可行流,与之前的流合并都一定是一条原图上的可行流,于是直接在残量网络上跑(s ightarrow t)的最大流,与之前的可行流合并即可。注意跑最大流的时候要把那条增加的(t ightarrow s)的边流量给清零。

模板:loj#116

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=4e4+10;
const int M=4e5+10;
const int inf=0x3f3f3f3f;
namespace flow{
	struct node{
		int v,f,nxt;
	}e[M];
	int tot;
	int s,t,cnt=1,cur[N],first[N],vis[N],dis[N];
	inline void add(int u,int v,int f){
		e[++cnt].v=v;e[cnt].f=f;e[cnt].nxt=first[u];first[u]=cnt;
	}
	inline void Add(int u,int v,int f){
		add(u,v,f);add(v,u,0);
	}
	inline bool bfs(){
		fill(vis+1,vis+tot+1,0);
		fill(dis+1,dis+tot+1,0);
		queue<int> q;q.push(s);
		dis[s]=0;vis[s]=1;
		while(!q.empty()){
			int u=q.front();q.pop();
			for(int i=first[u];i;i=e[i].nxt){
				int v=e[i].v;
				if(e[i].f>0&&!vis[v]){
					vis[v]=1;dis[v]=dis[u]+1;
					q.push(v);
				}
			}
		}
		return vis[t];
	}
	inline int dfs(int u,int f){
		if(u==t||!f) return f;
		int used=0;
		for(int &i=cur[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(e[i].f>0&&dis[v]==dis[u]+1){
				int fl=dfs(v,min(f,e[i].f));
				used+=fl;f-=fl;
				e[i].f-=fl;e[i^1].f+=fl;
				if(!f) break;
			}
		}
		if(f) dis[u]=inf;
		return used;
	}
	inline int dinic(int S,int T,int tp){
		if(tp) e[2].f=e[3].f=0;
		int ans=0;s=S;t=T;
		while(bfs()){
			memcpy(cur,first,sizeof(first));
			ans+=dfs(s,inf);
		}
		return ans;
	}
	inline void init(){
		fill(first+1,first+tot+1,0);tot=0;
		cnt=1;
	}
}
using flow::tot;
using flow::Add;
using flow::cnt;
int n,m,s,t,S,T,w[N],pos[N];
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	tot=n;S=++tot;T=++tot;
	Add(t,s,inf);
	for(int i=1;i<=m;++i){
		int u,v,l,r;
		scanf("%d%d%d%d",&u,&v,&l,&r);
		Add(u,v,r-l);w[u]-=l;w[v]+=l;
	}
	int sum=0;
	for(int i=1;i<=n;++i){
		if(w[i]>0) sum+=w[i],Add(S,i,w[i]);
		if(w[i]<0) Add(i,T,-w[i]);
	}
	if(flow::dinic(S,T,0)!=sum){
		puts("please go home to sleep");
		return 0;
	}
	else printf("%d
",flow::e[3].f+flow::dinic(s,t,1));
	return 0;
}#include<bits/stdc++.h>
using namespace std;
const int N=4e4+10;
const int M=4e5+10;
const int inf=0x3f3f3f3f;
namespace flow{
	struct node{
		int v,f,nxt;
	}e[M];
	int tot;
	int s,t,cnt=1,cur[N],first[N],vis[N],dis[N];
	inline void add(int u,int v,int f){
		e[++cnt].v=v;e[cnt].f=f;e[cnt].nxt=first[u];first[u]=cnt;
	}
	inline void Add(int u,int v,int f){
		add(u,v,f);add(v,u,0);
	}
	inline bool bfs(){
		fill(vis+1,vis+tot+1,0);
		fill(dis+1,dis+tot+1,0);
		queue<int> q;q.push(s);
		dis[s]=0;vis[s]=1;
		while(!q.empty()){
			int u=q.front();q.pop();
			for(int i=first[u];i;i=e[i].nxt){
				int v=e[i].v;
				if(e[i].f>0&&!vis[v]){
					vis[v]=1;dis[v]=dis[u]+1;
					q.push(v);
				}
			}
		}
		return vis[t];
	}
	inline int dfs(int u,int f){
		if(u==t||!f) return f;
		int used=0;
		for(int &i=cur[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(e[i].f>0&&dis[v]==dis[u]+1){
				int fl=dfs(v,min(f,e[i].f));
				used+=fl;f-=fl;
				e[i].f-=fl;e[i^1].f+=fl;
				if(!f) break;
			}
		}
		if(f) dis[u]=inf;
		return used;
	}
	inline int dinic(int S,int T,int tp){
		if(tp) e[2].f=e[3].f=0;
		int ans=0;s=S;t=T;
		while(bfs()){
			memcpy(cur,first,sizeof(first));
			ans+=dfs(s,inf);
		}
		return ans;
	}
	inline void init(){
		fill(first+1,first+tot+1,0);tot=0;
		cnt=1;
	}
}
using flow::tot;
using flow::Add;
using flow::cnt;
int n,m,s,t,S,T,w[N],pos[N];
int main(){
	while(scanf("%d%d",&n,&m)!=EOF){
		fill(w+1,w+tot+1,0);
		flow::init();
		s=++tot;t=++tot;S=++tot;T=++tot;
		Add(t,s,inf); 
		for(int i=1,g;i<=m;++i){
			scanf("%d",&g);
			tot++;
			Add(tot,t,inf);
			w[tot]-=g;w[t]+=g;
		}
		for(int i=1,c,d;i<=n;++i){
			scanf("%d%d",&c,&d);
			tot++;
			Add(s,tot,d);
			for(int j=1,t,l,r;j<=c;++j){
				scanf("%d%d%d",&t,&l,&r);t++;
				Add(tot,t+4,r-l);
				w[t+4]+=l;w[tot]-=l;
			}
		}
		int sum=0;
		for(int i=1;i<=tot;++i){
			if(w[i]>0) sum+=w[i],Add(S,i,w[i]);
			if(w[i]<0) Add(i,T,-w[i]);
		}
		if(flow::dinic(S,T,0)!=sum){
			puts("-1
");
			continue; 
		}
		printf("%d

",flow::e[3].f+flow::dinic(s,t,1));
	}
	return 0;
}

五、有源汇上下界最小流

和上个问题没什么区别,先跑出一个可行流,然后在残量网络上跑(t ightarrow s)的最大流,将这最大流的路径上所有道路的流量都撤回,依然得到一个可行流。因此答案就是可行流减去反向最大流。

模板:loj #117

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10;
const int M=4e5+10;
const int inf=0x3f3f3f3f;
namespace flow{
	struct node{
		int v,f,nxt;
	}e[M];
	int tot;
	int s,t,cnt=1,cur[N],first[N],vis[N],dis[N];
	inline void add(int u,int v,int f){
		e[++cnt].v=v;e[cnt].f=f;e[cnt].nxt=first[u];first[u]=cnt;
	}
	inline void Add(int u,int v,int f){
		add(u,v,f);add(v,u,0);
	}
	inline bool bfs(){
		fill(vis+1,vis+tot+1,0);
		fill(dis+1,dis+tot+1,0);
		queue<int> q;q.push(s);
		dis[s]=0;vis[s]=1;
		while(!q.empty()){
			int u=q.front();q.pop();
			for(int i=first[u];i;i=e[i].nxt){
				int v=e[i].v;
				if(e[i].f>0&&!vis[v]){
					vis[v]=1;dis[v]=dis[u]+1;
					q.push(v);
				}
			}
		}
		return vis[t];
	}
	inline int dfs(int u,int f){
		if(u==t||!f) return f;
		int used=0;
		for(int &i=cur[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(e[i].f>0&&dis[v]==dis[u]+1){
				int fl=dfs(v,min(f,e[i].f));
				used+=fl;f-=fl;
				e[i].f-=fl;e[i^1].f+=fl;
				if(!f) break;
			}
		}
		if(f) dis[u]=inf;
		return used;
	}
	inline int dinic(int S,int T,int tp){
		if(tp) e[2].f=e[3].f=0;
		int ans=0;s=S;t=T;
		while(bfs()){
			memcpy(cur,first,sizeof(first));
			ans+=dfs(s,inf);
		}
		return ans;
	}
	inline void init(){
		fill(first+1,first+tot+1,0);tot=0;
		cnt=1;
	}
}
using flow::tot;
using flow::Add;
using flow::cnt;
int n,m,s,t,S,T,w[N],pos[N];
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	tot=n;S=++tot;T=++tot;
	Add(t,s,inf);
	for(int i=1;i<=m;++i){
		int u,v,l,r;
		scanf("%d%d%d%d",&u,&v,&l,&r);
		Add(u,v,r-l);w[u]-=l;w[v]+=l;
	}
	int sum=0;
	for(int i=1;i<=n;++i){
		if(w[i]>0) sum+=w[i],Add(S,i,w[i]);
		if(w[i]<0) Add(i,T,-w[i]);
	}
	if(flow::dinic(S,T,0)!=sum){
		puts("please go home to sleep");
		return 0;
	}
	else printf("%d
",flow::e[3].f-flow::dinic(t,s,1));
	return 0;
}
原文地址:https://www.cnblogs.com/tqxboomzero/p/14403469.html