图论-网络流⑧-有上下界的网络流

图论-网络流⑧-有上下界的网络流

上一篇:图论-网络流⑦-费用流解题

参考文献:

https://www.cnblogs.com/DuskOB/p/11216861.html
https://www.cnblogs.com/-xiangyang/p/9773887.html
https://www.cnblogs.com/dreagonm/p/10803040.html
https://www.luogu.com.cn/problemnew/solution/P4043

大纲

  • 什么是网络流
  • 最大流(最小割)
  • D i n i c Dinic Dinic (常用)
  • E K EK EK
  • S a p Sap Sap
  • F o r d − F u l k e r s o n Ford-Fulkerson FordFulkerson(不讲)
  • H L P P HLPP HLPP (快)
  • 最大流解题
  • 费用流
  • E K EK EK 费用流
  • D i n i c Dinic Dinic 费用流
  • z k w zkw zkw 费用流
  • 费用流解题

  • 有上下界的网络流 Start \color{#33cc00}\texttt{Start} Start

  • 无源汇上下界可行流
  • 有源汇上下界可行流
  • 有源汇上下界最大流
  • 有源汇上下界最小流
  • 最大权闭合子图
  • 有上下界的网络流解题 End \color{red}\texttt{End} End

上篇中讲完了费用流,这篇会讲一种特殊网络流:有上下界的网络流。

有上下界的网络流

普通的网络流(包括最大流和费用流)的边只有最大流量限制,默认最小流量限制为 0 0 0,但是你有没有想过最小流量限制不为 0 0 0 的网络最大流或网络费或可行流(满足限制的流)用流怎么做呢?

无源汇上下界可行流

网络流图中没有源点和汇点,每条边 i i i 的流量都被限制在区间 [ L i , R i ] [L_i,R_i] [Li,Ri]中,问该网络中达到可行流量时每条边的流量(找一个方案)。模板:传送门

对于第 i i i 条边 ( u , v ) (u,v) (u,v),因为其流量区间大小为 R i − L i + 1 R_i-L_i+1 RiLi+1,正好等于普通网络流流量为 R i − L i R_i-L_i RiLi 时的区间大小,所以在网络流图中先连 ( u , v ) (u,v) (u,v),流量为 R i − L i R_i-L_i RiLi 以限制这条边的流量变化区间。

如果每个 L i = 0 L_i=0 Li=0,那么这样就已经对了。可是因为网络流需要满足每个节点的流入流量等于流出流量,所以记录 d [ x ] = ∑ i ∈ { x 的 入 边 } L i − ∑ i ∈ { x 的 出 边 } L i d[x]=\sum\limits _{i\in\{x的入边\}}L_i-\sum\limits_{i\in\{x的出边\}}L_i d[x]=i{x}Lii{x}Li 。然后建立超级源点 S S S 和超级汇点 T T T。如果 d [ x ] > 0 d[x]>0 d[x]>0,就连 ( S , x ) (S,x) (S,x),流量为 d [ x ] d[x] d[x];如果 d [ x ] < 0 d[x]<0 d[x]<0,就连 ( x , T ) (x,T) (x,T),流量为 − d [ x ] -d[x] d[x]。这样就起到了一个补流的作用, x x x 节点于是就这么流量守恒了。

同时,记录 s u m = ∑ x ∈ 图 节 点 & & d [ x ] > 0 d [ x ] sum=\sum\limits_{x\in图节点\&\&d[x]>0}d[x] sum=x&&d[x]>0d[x]
如果上面的网络流图跑出来的最大流 ≠ s u m \neq sum =sum,说明不存在可行流。否则,原图(不包括补流边)每条边 i i i 在可行流中的流量可以是 f w [ x ⊕ 1 ] + L i fw[x\oplus 1]+L_i fw[x1]+Li,其中 f w [ x ⊕ 1 ] fw[x\oplus 1] fw[x1] 表示这条边在最大流运行后实际流了的流量。

整理一下:

原图:
mcmf6.jpg
转化后图:
kxl1.jpg
答案可行流:
kxl2.jpg

代码:

#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
template<int V,int M>
class Dinic{
public:int E,g[V],to[M],nex[M],fw[M];
	void clear(){memset(g,0,sizeof g),E=1;} 
	Dinic(){clear();}
	void add(int x,int y,int f){nex[++E]=g[x],to[E]=y,fw[E]=f,g[x]=E;}
	void Add(int x,int y,int f){add(x,y,f),add(y,x,0);}
	int dep[V],cur[V];bool vis[V];queue<int> q;
	bool bfs(int s,int t,int p){
		for(int i=1;i<=p;i++) vis[i]=0,cur[i]=g[i];
		q.push(s),vis[s]=1,dep[s]=0;
		while(q.size()){
			int x=q.front();q.pop();
			for(int i=g[x];i;i=nex[i])if(!vis[to[i]]&&fw[i])
				q.push(to[i]),vis[to[i]]=1,dep[to[i]]=dep[x]+1;
		}
		return vis[t]; 
	}
	int dfs(int x,int t,int F){
		if(x==t||!F) return F;
		int f,flow=0;
		for(int&i=cur[x];i;i=nex[i])
			if(dep[to[i]]==dep[x]+1&&(f=dfs(to[i],t,min(F,fw[i])))>0)
				{fw[i]-=f,fw[i^1]+=f,F-=f,flow+=f;if(!F) break;}
		return flow;
	}
	int dinic(int s,int t,int p){
		int res=0,f;
		while(bfs(s,t,p)) while((f=dfs(s,t,inf))) res+=f;
		return res;
	}
};
int n,m,s,t,p,d[210],sum,low[40010];
vector<int> oedge;
Dinic<220,40010> net;
int main(){
	scanf("%d%d",&n,&m);
	p=t=n+2,s=t-1;
	for(int i=1,x,y,l,r;i<=m;i++){
		scanf("%d%d%d%d",&x,&y,&l,&r);
		net.Add(x,y,r-l);
		d[x]-=l,d[y]+=l;
		low[net.E]=l;
		oedge.push_back(net.E);
	}
	for(int i=1;i<=n;i++)
		if(d[i]>0) net.Add(s,i,d[i]),sum+=d[i];
		else if(d[i]<0) net.Add(i,t,-d[i]);
	if(net.dinic(s,t,p)!=sum) return puts("NO"),0;
	else {
		puts("YES");
		for(auto i:oedge)
			printf("%d\n",net.fw[i]+low[i]);
	}
	return 0;
}

总结:比较偏理论吧。

有源汇上下界可行流

网络流图中有源点 s s s 和汇点 t t t,每条边 i i i 的流量都被限制在区间 [ L i , R i ] [Li,Ri] [Li,Ri] 中,问该网络中达到可行流量时每条边的流量以及 s s s t t t 的流量。

如果连 ( t , s ) (t,s) (t,s) 流量为 ∞ \infty ,就转化成了无源汇上下界可行流问题,然后增加超级源汇补流边,解法同上,代码同上。

有源汇上下界最大流

网络流图中有源点 s s s 和汇点 t t t,每条边 i i i 的流量都被限制在区间 [ L i , R i ] [Li,Ri] [Li,Ri] 中,问该网络中达到可行最大流量时每条边的流量以及 s s s t t t 的流量。

先跑一遍有源汇上下界可行流。如果可行,就用残余跑一次最大流,然后两个结果相加就是有源汇上下界最大流。解法同上,代码同上。

真的不是在敷衍你,就是这样的!

有源汇上下界最小流

比较复杂。先建超级源点 S S S,超级汇点 T T T,然后按照规则连补流边,对 S → T S\to T ST 跑一次最大流。然后对原图连一条 ( t , s ) (t,s) (t,s) 的流量为 ∞ \infty 的边,然后再在残余网络上跑一次 S → T S\to T ST 的最大流。然后答案就是最后加的 ( t , s ) (t,s) (t,s) 边实际流的流量。 模板:传送门

原理就是先跑个可行流然后不停缩减流量。

为了更好理解,放个代码:

#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
template<int V,int M>
class Dinic{
public:int E,g[V],to[M],nex[M],fw[M];
	void clear(){memset(g,0,sizeof g),E=1;} 
	Dinic(){clear();}
	void add(int x,int y,int f){nex[++E]=g[x],to[E]=y,fw[E]=f,g[x]=E;}
	void Add(int x,int y,int f){add(x,y,f),add(y,x,0);}
	int dep[V],cur[V];bool vis[V];queue<int> q;
	bool bfs(int s,int t,int p){
		for(int i=1;i<=p;i++) vis[i]=0,cur[i]=g[i];
		q.push(s),vis[s]=1,dep[s]=0;
		while(q.size()){
			int x=q.front();q.pop();
			for(int i=g[x];i;i=nex[i])if(!vis[to[i]]&&fw[i])
				q.push(to[i]),vis[to[i]]=1,dep[to[i]]=dep[x]+1;

		}
		return vis[t];
	}
	int dfs(int x,int t,int F){
		if(x==t||!F) return F;
		int f,flow=0;
		for(int&i=cur[x];i;i=nex[i])
			if(dep[to[i]]==dep[x]+1&&(f=dfs(to[i],t,min(F,fw[i])))>0)
				{fw[i]-=f,fw[i^1]+=f,F-=f,flow+=f;if(!F) break;}
		return flow;
	}
	int dinic(int s,int t,int p){
		int res=0,f;
		while(bfs(s,t,p)) while((f=dfs(s,t,inf))) res+=f;
		return res;
	}
};
const int N=6e4+10;
const int M=5e5+10;
int n,m,s,t,S,T,p,d[N],sum,ans;
Dinic<N,M> net;
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	p=T=n+2,S=T-1;
	for(int i=1,x,y,l,r;i<=m;i++){
		scanf("%d%d%d%d",&x,&y,&l,&r);
		net.Add(x,y,r-l);
		d[x]-=l,d[y]+=l;
	}
	for(int i=1;i<=n;i++)
		if(d[i]>0) net.Add(S,i,d[i]),sum+=d[i];
		else if(d[i]<0) net.Add(i,T,-d[i]);
	ans+=net.dinic(S,T,p); //三行核心代码
	net.Add(t,s,inf);      //三行核心代码
	ans+=net.dinic(S,T,p); //三行核心代码
	if(ans!=sum) puts("please go home to sleep");
	else printf("%d\n",net.fw[net.E]);
	return 0;
}

最大权闭合子图

闭合图是在一个图中,选取一些点构成点集,若集合中任意点连接的出边所通往的点也在该点集中,则这个点集以及所有这些边构成闭合图。最大权闭合子图即点权之和最大的闭合图。

要求一个图的最大权闭合子图,先建立源点 s s s 和汇点 t t t然后连 ( s , (s, (s, 正权点 ) ) ),流量为点的权值;连 ( ( (负权点 , t ) ,t) ,t) ,流量为点权的相反数。点与点之间按照边原来的方向连流量为 ∞ \infty 的边。

然后该图的最大权闭合子图就为 ( ( (正权点权值之和 − - 网络流图最大流 ) ) )

有上下界的网络流解题

该算法其实题目较少,属于冷门算法。但蒟蒻找了很久还是找到了一道好例题。

[AHOI2014/JSOI2014]支线剧情

看了题目,你会谔谔道:这是有上下界的费用流!你是对的,如果你掌握了有上下界的最大流,也就掌握了有上下界的费用流。


对于像有上下界的网络流这样的偏理论性算法,应该做到触类旁通,这题虽然是“有上下界的费用流”,但和有上下界的最大流本质是一样的。

很明显,为了先构建一个有上下界的网络最大流模型,要增加汇点 t ′ t' t(因为 t t t 是题面中的变量,所以不能用),使所有不是树根节点的节点可以连到 t ′ t' t,流量范围 [ 0 , ∞ ] [0,\infty] [0,],费用 0 0 0,以确保看完剧情。然后对于每条树上边,看作流量下限为 1 1 1 上限为 ∞ \infty ,费用为 t i , j t_{i,j} ti,j 的费用流边。

然后按照解决有有源汇有上下界的网络可行流的方法,跑个最小费用可行流就好了。代码如下:

#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
template<int V,int M>
class zkwmcmf{
public:int E,g[V],to[M],nex[M],fw[M],ct[M],fans,cans;
	void clear(){memset(g,0,sizeof g),E=1,fans=cans=0;}
	zkwmcmf(){clear();}
	void add(int x,int y,int f,int c){
		nex[++E]=g[x],to[E]=y,fw[E]=f,ct[E]=c,g[x]=E;
	}
	void Add(int x,int y,int f,int c){add(x,y,f,c),add(y,x,0,-c);}
	int dep[V];bool vis[V];deque<int> q;
	bool spfa(int s,int t,int p){
		for(int i=1;i<=p;i++) vis[i]=0,dep[i]=inf;
		q.push_back(t),dep[t]=0,vis[t]=1;
		while(q.size()){
			int x=q.front();q.pop_front(),vis[x]=0;
			for(int i=g[x];i;i=nex[i])
				if(fw[i^1]&&dep[to[i]]>dep[x]-ct[i]){
					dep[to[i]]=dep[x]-ct[i];
					if(!vis[to[i]]){
						vis[to[i]]=1;
						if(q.size()&&dep[to[i]]<dep[q.front()])
							q.push_front(to[i]);
						else q.push_back(to[i]);
					}
				}
		}
		return dep[s]<inf;
	}
	int dfs(int x,int t,int F){
		vis[x]=1;
		if(x==t||!F) return F;
		int f,flow=0;
		for(int i=g[x];i;i=nex[i])if(fw[i]&&!vis[to[i]]&&
		dep[to[i]]==dep[x]-ct[i]&&(f=dfs(to[i],t,min(F,fw[i])))>0)
		{cans+=ct[i]*f,fw[i]-=f,fw[i^1]+=f,F-=f,flow+=f;if(!F)break;}
		return flow;
	}
	void mcmf(int s,int t,int p){
		while(spfa(s,t,p)){
			vis[t]=1;
			while(vis[t]){
				memset(vis,0,sizeof vis);
				fans+=dfs(s,t,inf);
			}
		}
	}
};
int n,ans,d[510],s,t,p;
zkwmcmf<510,100010> net;
int main(){
	scanf("%d",&n);
	p=t=n+3,s=t-1;
	for(int i=1,x,y,z;i<=n;i++){
		scanf("%d",&x);
		for(int j=1;j<=x;j++){
			scanf("%d%d",&y,&z);
			d[i]-=1,d[y]+=1,ans+=z;
			net.Add(i,y,inf,z);
		}
	}
	for(int i=2;i<=n;i++) net.Add(i,n+1,inf,0);
	for(int i=1;i<=n;i++){
		if(d[i]>0) net.Add(s,i,d[i],0);
		if(d[i]<0) net.Add(i,t,-d[i],0);
	}
	net.Add(n+1,1,inf,0);
	//代码其实就是一模一样的,费用流,最大流,到头来还是同个东西
	net.mcmf(s,t,p);
	printf("%d\n",net.cans+ans);
	return 0;
}

总结:费用流,最大流,到头来还是同个东西。

然后这次网络流就讲到这里了,没有网络流⑨了。如果喜欢蒟蒻写的博文,就点个赞。蒟蒻后期还会写最小割的高级知识 G H T GHT GHT(最小割树)的,感谢你的支持。

祝大家学习愉快!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值