【学习笔记】上下界网络流

27 篇文章 0 订阅

零、前言

昨天刚刚被神秘建图方式打败了。今天又发现连知识点都没学完……

学习知料:“人菜无水平” 国集学长太阳神看上去像法语的家伙网络流建模基础(只是挂个链接)、“简单的网络流”(又是 “人菜无水平” 学长的)。

壹、无源汇可行流

1. 问题

每条边有 l o w ( u , v ) low(u,v) low(u,v) 需要满足 l o w ( u , v ) ⩽ f ( u , v ) ⩽ c ( u , v ) low(u,v)\leqslant f(u,v)\leqslant c(u,v) low(u,v)f(u,v)c(u,v),并且每个点都流量守恒(即,流量构成了很多环)。

2. 解法

首先加入源点 S S S 和汇点 T T T 。这是两个新加入的点,不需要流量守恒啥的。

按照 T r y M y E d g e \sf TryMyEdge TryMyEdge 的讲法,最小流量可以看做传送门, l o w ( u , v ) low(u,v) low(u,v) 变成 u → T u\rightarrow T uT 而后 S → v S\rightarrow v Sv,容量均为 l o w ( u , v ) low(u,v) low(u,v) 即可。

按照 太阳神 的讲法, u → T u\rightarrow T uT S → v S\rightarrow v Sv 就是为了补充他们的流量出度、入度,剩下的 ⟨ u , v ⟩ \lang u,v\rang u,v 容量为 c ( u , v ) − l o w ( u , v ) c(u,v)-low(u,v) c(u,v)low(u,v) 可以随便流。

做完了这个图的转换,就成了经典的网络流,直接跑最大流即可。——只是要记得此时图中的流量并非原图的流量。

我觉得这就讲的够清楚了。如果你非要再想想,那就是,如果有可行流,新图中 S S S 的出边可以满流。把原本的流量拆解到 u → T u\rightarrow T uT S → v S\rightarrow v Sv 即可。反之亦然。故,这是 等价关系

3. 优化

由于一个点有出边也有入边,很可能会连出 S → u → T S\rightarrow u\rightarrow T SuT 的情况。生活经验 网络流基本知识告诉我们,这样的边会直接流满一条。而我们只求 S S S 的出边是否能满流,所以可以换一种建边的方式:对于每个 u u u,直接统计 ∑ l o w ( v , u ) \sum low(v,u) low(v,u) ∑ l o w ( u , v ) \sum low(u,v) low(u,v),根据大小关系选择连 S S S 还是连 T T T,容量为差值。

贰、有源汇可行流

1. 问题

每条边的流量仍然有最小和最大,除了源点和汇点之外的点仍然流量守恒。源点可以流出多于流入,汇点可以流入多于流出。(不难发现,源点的净流出等于汇点净流入。)

2. 解法

这个我就不剽窃别人的讲法了。我试着自己讲一讲。讲的不好 活该你看我博客 就去康 学习知料 里的吧。(主要是文风很大程度上受到了史铁生的影响……)

模仿上面的做法。新建超级源点 S S S 和超级汇点 T T T,记原图中的源汇为 s , t s,t s,t 。之前用传送门,现在咱们用黑洞! s s s 流出多?不要紧! S → s S\rightarrow s Ss 帮你填坑! t t t 流入多?没问题! t → T t\rightarrow T tT 人在塔在!

你满心欢喜,认为这就可以了。然而你没想到的是,现在怎么判断 S S S 是否满流?如果你想要 s s s 任意流出,不等不令 S → s S\rightarrow s Ss 容量为 + ∞ +\infty + 啊。

好吧,咱们修改一下。这条边不满流不要紧,其他边满流就行!好主意。我斗胆再提醒一句,我们 求的是最大流,如果能满流,那么最大流肯定是它。现在有一条边不是满流,我们的最大流能够确保别的边满流吗?别的边不满流,也可能成为最大流嘛!没准儿存在一种其他边满流的情况,只是总流量较小呢?

行吧,看来 “黑洞” 计划泡汤了。仔细想想,为啥这样会出问题?因为我们不知道 s , t s,t s,t 之间会转移多少流量,所以 S S S s s s 的连边必须是 + ∞ +\infty + 。所以 我们把流量求出来 我们让网络流内部 自行调节。我们连边 t → s t\rightarrow s ts 容量为 + ∞ +\infty + 下界为 0 0 0 。此时再做 无源汇可行流,这条边会自发的调整为 s , t s,t s,t 之间转移的流量。

或者还用 T r y M y E d g e \sf TryMyEdge TryMyEdge 的讲法,之前我要用传送门,就是为了让资金流转有一个中介。(我们)作为中介,掌握资金的流转就是容易的事情,然后就掌握了下界的满足情况啊。可是 s , t s,t s,t 之间的流量,不需要我们去知道,只需要让它们(保持原样)存在,就像水在高处就流向低处,鸟扑打翅膀就会腾空一样自然啊。直接连接 s , t s,t s,t(而不是 S , T S,T S,T 参与其中),难道不是应当的吗?

回到理性的这一方来。类似的分析不难发现,可行流存在,必然有满流。反之亦然。

叁、上下界最大流

1. 问题

最大流当然是有源汇的啦。基本要求同 有源汇可行流,不过要求源点的净流出最大。

2. 解法

显然要先做 有源汇可行流,至少让它合法了再说。

然后呢?我好像不是很会最大流,除了 找增广路 啥也不会。行吧,那就暴力找。把 S , T S,T S,T 抛开不要, s , t s,t s,t 不就是源点和汇点吗?然后就能过题。信竞结论都要证明的话,跟数竞有什么区别?

事实上我们早已埋下伏笔。无源汇可行流 云: S S S 的出边满流,与存在可行流,这是等价关系。放在这里也一样。 S S S 的出边一直保持满流(我们没有去动它),增广时保持了其他点的流量守恒,所以 当前流永远是可行流

所以就直接增广就行啦!并不是很难吧?——更严谨一点:对于除了我以外的人,这难道是困难的吗?

3. 优化

由于 S S S 出边满流,只剩下了反向边,增广路本来就不会经过它的。 T T T 也是一样。所以不用特别地删除二者。包括 t → s t\rightarrow s ts 这条边,也会自动进入增广路径(其反向边),并且权值恰为可行流中的流量(在 有源汇可行流 中有提到),不删掉它反而比较方便——不需要加上可行流的流量了。

In a word, no delete. \text{In a word, no delete.} In a word, no delete. 我的天好押韵啊。

肆、上下界最小流

1. 问题

这个标题很难理解吗

2. 解法

t t t s s s 跑增广路,求出的 “最大流” 就是在可行流的基础上可以退掉的流量。只是请别忘了,现在 t → s t\rightarrow s ts 不得不被删掉,不然跑出来就是个 + ∞ +\infty +

不过这里有一个有趣的事情:什么是最小流?比如说 t → s t\rightarrow s ts 的流量为 10 10 10 是否应当看做 s → t s\rightarrow t st 流量为 − 10 -10 10 呢?应当是不可以的。所以净流出为负数时,应当判定为 0 0 0

伍、无负环上下界费用流

其实多数情况都只是略作修改而已。核心都是利用 f ′ = f − l o w f'=f-low f=flow,所以 ∑ f × c o s t = ∑ l o w × c o s t + ∑ f ′ × c o s t \sum f\times cost=\sum low\times cost+\sum f'\times cost f×cost=low×cost+f×cost ,左边 ∑ l o w × c o s t \sum low\times cost low×cost 为定值,最小化右边 ⇔ \Leftrightarrow 新图中的最小费用流(不管要求是什么)。

1. 无源汇最小费用可行流

然鹅跟 无源汇可行流 没啥区别。将 S → u S\rightarrow u Su u → T u\rightarrow T uT 费用都设置为 0 0 0,直接 S S S T T T 跑最小费用最大流即可。(别忘了我们是把可行流转化成了最大流。)

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

然鹅跟 有源汇可行流 没啥区别。把 t → s t\rightarrow s ts 的费用设置为 0 0 0,转化为了 无源汇最小费用可行流

3. 上下界最小费用最大流

然鹅跟 上下界最大流 没啥区别。也是先跑 有源汇最小费用可行流 ,然后增广最小费用最大流。

4. 上下界最小费用最小流

不想写了,因为我 “费” 字看的太多已经看不出它是个字了。

5.5.    ?    ? ?    \bm{\;?\;??\;} ???最大费用    ? ?    \bm{\;??\;} ??

边权取负,变为最小费用。没戳,就是介么暴力。

陆、 ∗ \color{red}^* 有负环费用流

并不是上下界了……但是由于 思路上有相似性 学长的博客里写了,我也只好写一写。

1. 无源汇最小费用流

考虑把负边权改为正边权。费用流中我们知道,反向边的费用是相反数。所以我们预先把负边流满,反向边则获得了我们拥有的流量。

此时这条边像不像 无源汇可行流 中的 必须流过的下界?毕竟我们规定它是流满了的!只是我们用反向边给了它 反悔 的机会罢了!

所以 ⟨ u , v ⟩ \langle u,v\rangle u,v 的连边方式仍然是 u → T ,    S → v u\rightarrow T,\;S\rightarrow v uT,Sv 。而后仍然跑最小费用最大流。(这里不用判断满流了,因为肯定会满流 ⇔ \Leftrightarrow 存在可行流。)

2. 有源汇最小费用流

类似 无源汇最小费用流,只是额外加入 t → s t\rightarrow s ts 罢了。如 太阳神 之语:“这是常见套路。”

柒、例题与代码

“第一次学网络流,模板题得 9 9 9 分就算成功,找什么例题啊?”—— T r y M y E d g e \sf TryMyEdge TryMyEdge

无源汇有上下界可行流

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 205;
const int MaxM = 10202;
struct Edge{
	int to, nxt, val;
	Edge(){ /* nothing */; }
	Edge(int T,int N,int V){
		to = T, nxt = N, val = V;
	}
};
Edge e[MaxM<<2];
int head[MaxN], cntEdge;
void addEdge(int a,int b,int c){
	e[cntEdge] = Edge(b,head[a],c);
	head[a] = cntEdge ++;
	e[cntEdge] = Edge(a,head[b],0);
	head[b] = cntEdge ++;
}

int dis[MaxN]; queue< int > q;
bool bfs(int x,const int &t,int n){
	memset(dis,-1,n<<2);
	dis[x] = 0; q.push(x);
	while(!q.empty()){
		x = q.front(); q.pop();
//		printf("x = %d with dis = %d\n",x,dis[x]);
		for(int i=head[x]; ~i; i=e[i].nxt)
			if(!(~dis[e[i].to]) && e[i].val){
				dis[e[i].to] = dis[x]+1;
				q.push(e[i].to);
			}
	}
	return dis[t] != -1;
}
int cur[MaxN]; // curly optimization
int dfs(int x,int inFlow,const int &t){
	int sum = 0; if(x == t) return inFlow;
	for(int &i=cur[x]; ~i; i=e[i].nxt){
		if(dis[e[i].to] == dis[x]+1 && e[i].val){
			int d = min(inFlow-sum,e[i].val);
			e[i].val -= (d = dfs(e[i].to,d,t));
			e[i^1].val += d; sum += d;
			if(sum == inFlow) break; // gone
		}
	}
	if(!sum) dis[x] = -1;
//	printf("%lld return %lld\n",x,sum);
	return sum;
}
const int infty = (1<<30)-1;
int dinic(int s,int t,int n){
	int tiny_sy = 0;
	while(bfs(s,t,n)){
		memcpy(cur,head,n<<2);
		tiny_sy += dfs(s,infty,t);
	}
	return tiny_sy;
}

int low[MaxM<<2], deg[MaxN];
signed main(){
	int n = readint(), m = readint();
	memset(head,-1,MaxN<<2);
	for(int u,v,l,c,i=0; i<m; ++i){
		u = readint(), v = readint();
		l = readint(), c = readint();
		low[cntEdge] = l;
		addEdge(u,v,c-l);
		deg[u] -= l, deg[v] += l;
	}
	int check = 0;
	for(int i=1; i<=n; ++i)
		if(deg[i] > 0){
			addEdge(0,i,deg[i]);
			check += deg[i];
		}
		else if(deg[i] < 0)
			addEdge(i,n+1,-deg[i]);
	if(dinic(0,n+1,n+2) != check){
		puts("NO"); return 0;
	} else puts("YES");
	for(int i=0; i<m; ++i)
		printf("%d\n",e[i<<1|1].val+low[i<<1]);
	return 0;
}

有源汇有上下界最大流

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 205;
const int MaxM = 10202;
struct Edge{
	int to, nxt, val;
	Edge(){ /* nothing */; }
	Edge(int T,int N,int V){
		to = T, nxt = N, val = V;
	}
};
Edge e[MaxM<<2];
int head[MaxN], cntEdge;
void addEdge(int a,int b,int c){
	e[cntEdge] = Edge(b,head[a],c);
	head[a] = cntEdge ++;
	e[cntEdge] = Edge(a,head[b],0);
	head[b] = cntEdge ++;
}

int dis[MaxN]; queue< int > q;
bool bfs(int x,const int &t,int n){
	memset(dis,-1,n<<2);
	dis[x] = 0; q.push(x);
	while(!q.empty()){
		x = q.front(); q.pop();
//		printf("x = %d with dis = %d\n",x,dis[x]);
		for(int i=head[x]; ~i; i=e[i].nxt)
			if(!(~dis[e[i].to]) && e[i].val){
				dis[e[i].to] = dis[x]+1;
				q.push(e[i].to);
			}
	}
	return dis[t] != -1;
}
int cur[MaxN]; // curly optimization
int dfs(int x,int inFlow,const int &t){
	int sum = 0; if(x == t) return inFlow;
	for(int &i=cur[x]; ~i; i=e[i].nxt){
		if(dis[e[i].to] == dis[x]+1 && e[i].val){
			int d = min(inFlow-sum,e[i].val);
			e[i].val -= (d = dfs(e[i].to,d,t));
			e[i^1].val += d; sum += d;
			if(sum == inFlow) break;
		}
	}
	if(!sum) dis[x] = -1; return sum;
}
const int infty = (1<<30)-1;
int dinic(int s,int t,int n){
	int tiny_sy = 0;
	while(bfs(s,t,n)){
		memcpy(cur,head,n<<2);
		tiny_sy += dfs(s,infty,t);
	}
	return tiny_sy;
}

int low[MaxM<<2], deg[MaxN];
signed main(){
	int n = readint(), m = readint();
	int s = readint(), t = readint();
	memset(head,-1,MaxN<<2);
	for(int u,v,l,c,i=0; i<m; ++i){
		u = readint(), v = readint();
		l = readint(), c = readint();
		low[cntEdge] = l;
		addEdge(u,v,c-l);
		deg[u] -= l, deg[v] += l;
	}
	int check = 0;
	for(int i=1; i<=n; ++i)
		if(deg[i] > 0){
			addEdge(0,i,deg[i]);
			check += deg[i];
		}
		else if(deg[i] < 0)
			addEdge(i,n+1,-deg[i]);
	addEdge(t,s,infty); // to return
	if(dinic(0,n+1,n+2) != check){
		puts("please go home to sleep");
		return 0; // take a shower
	}
	else printf("%d\n",dinic(s,t,n+2));
	return 0;
}

有源汇有上下界最小流

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 50010;
const int MaxM = 125010;
struct Edge{
	int to, nxt; int_ val;
	Edge(){ /* nothing */; }
	Edge(int T,int N,int_ V){
		to = T, nxt = N, val = V;
	}
};
Edge e[MaxM<<2];
int head[MaxN], cntEdge;
void addEdge(int a,int b,int_ c){
	e[cntEdge] = Edge(b,head[a],c);
	head[a] = cntEdge ++;
	e[cntEdge] = Edge(a,head[b],0);
	head[b] = cntEdge ++;
}

int dis[MaxN]; queue< int > q;
bool bfs(int x,const int &t,int n){
	memset(dis,-1,n<<2);
	dis[x] = 0; q.push(x);
	while(!q.empty()){
		x = q.front(); q.pop();
//		printf("x = %d with dis = %d\n",x,dis[x]);
		for(int i=head[x]; ~i; i=e[i].nxt)
			if(!(~dis[e[i].to]) && e[i].val){
				dis[e[i].to] = dis[x]+1;
				q.push(e[i].to);
			}
	}
	return dis[t] != -1;
}
int cur[MaxN]; // curly optimization
int_ dfs(int x,int_ inFlow,const int &t){
	int_ sum = 0; if(x == t) return inFlow;
	for(int &i=cur[x]; ~i; i=e[i].nxt){
		if(dis[e[i].to] == dis[x]+1 && e[i].val){
			int_ d = min(inFlow-sum,0ll+e[i].val);
			e[i].val -= (d = dfs(e[i].to,d,t));
			e[i^1].val += d; sum += d;
			if(sum == inFlow) break;
		}
	}
	if(!sum) dis[x] = -1; return sum;
}
const int_ infty = (1ll<<60)-1;
int_ dinic(int s,int t,int n){
	int_ tiny_sy = 0;
	while(bfs(s,t,n)){
		memcpy(cur,head,n<<2);
		tiny_sy += dfs(s,infty,t);
	}
	return tiny_sy;
}

int low[MaxM<<2]; int_ deg[MaxN];
signed main(){
	int n = readint(), m = readint();
	int s = readint(), t = readint();
	memset(head,-1,MaxN<<2);
	for(int u,v,l,c,i=0; i<m; ++i){
		u = readint(), v = readint();
		l = readint(), c = readint();
		low[cntEdge] = l;
		addEdge(u,v,c-l);
		deg[u] -= l, deg[v] += l;
	}
	int_ check = 0;
	for(int i=1; i<=n; ++i)
		if(deg[i] > 0){
			addEdge(0,i,deg[i]);
			check += deg[i];
		}
		else if(deg[i] < 0)
			addEdge(i,n+1,-deg[i]);
	addEdge(t,s,infty); // to return
	if(dinic(0,n+1,n+2) != check){
		puts("please go home to sleep");
		return 0; // take a shower
	}
	check = e[head[s]].val; // current flow
	head[s] = e[head[s]].nxt; // delete
	head[t] = e[head[t]].nxt; // delete
	printf("%lld\n",max(0ll,check-dinic(t,s,n+2)));
	return 0;
}

网络流二十四题

别指望这里会有二十四份代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值