上下界网络流问题

Preface

一直感觉这东西很高大上,事实上也确实如此。
反正是不知道打错时应该怎么调的了。

上下界

其实就是每条边的流量限制,普通的网络流有上界限制。
但如果有下界限制,问题就显得比较精彩了。

既然有了下界限制,那么对于这个网络流而言,是否有可行流都需要讨论。

无源汇上下界可行流

我们先对可行流进行讨论。

首先,解释一下“无源汇”的意思:把普通有源汇的图转化成每一个点都是源或汇,都可以有无限流量,但必须要满足流量平衡

而这个问题的定义就是:

能否对每条边进行流量定量,使得每个点满足流量平衡的前提下,每条边也在流量的上下界范围内。

请注意,我们现在关心的仅仅是这个图是否存在一种可行流。

这个问题也是上下界网络流一类问题的核心。所以我将重点分析一下解决这个问题的思路。

解决此问题的核心思想,是调整

先确定一个看似合法的流,即每条边都已经有一个流量下界了。很多情况下也称这个看似合法的流为初始流,本文也将这样称呼。

但注意到这个流虽然满足了下界,但却违背了流量平衡的限制。

于是,我们想办法通过对一些进行流量调整,然后使得最终流量平衡。

我们定义一个点的 A i A_i Ai为它在初始流中的流入量 - 流出量的值

分类讨论,如果 A i &lt; 0 A_i\lt 0 Ai<0那么表示 初始流中的流入量小于流出量,我们调整边的流量时需要 多流入这个点一些流量,然而这些流量我们必须要找到一个出路,否则又不满足流量平衡了。于是自然而然的想到设立所谓的超级汇。同理,当 A i &gt; 0 A_i\gt 0 Ai>0时,我们有理由设立所谓的超级源,因为我们需要让流量有一个来路

于是,根据上面的分析,我们可以自然而然的想到判定上下界是否有可行流的方法:

如果源点的所有出边都流满,或者说汇点所有的入边也流满,那么则证明有可行流。

#include <bits/stdc++.h>

#define F(i,a,b) for(int i=a;i<=b;i++)
#define mem(a,b) memset(a, b, sizeof a)

const int N = 2e2 + 10, M = 5e5 + 10;

using namespace std;

int n, m, s, t, lower, upper, ss, tt, tot, W[M]; bool bz[M];
int d[N], tov[M], nex[M], las[N], len[M], L[M], dis[N];

void link(int x, int y, int l) {
	tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = l;
	tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0;
}

bool OK() {
	queue <int> Q; Q.push(ss); mem(dis,7), dis[ss] = 0;
	while (Q.size()) {
		int k = Q.front(); Q.pop();
		for (int x = las[k]; x ; x = nex[x])
			if (len[x] && dis[tov[x]] > dis[k] + 1)
				dis[tov[x]] = dis[k] + 1, Q.push(tov[x]);
	}
	return dis[tt] < 117901063;
}

int Dinic(int k, int flow) {
	if (k == tt)
		return flow;
	int have = 0;
	for (int x = las[k]; x ; x = nex[x])
		if (len[x] && dis[tov[x]] == dis[k] + 1 && !bz[x]) {
			int now = Dinic(tov[x], min(flow - have, len[x]));
			have += now, len[x] -= now, len[x ^ 1] += now;
			if (flow == have)
				return flow;
		}
	if (!have) dis[k] = - 1;
	return have;
}

int main() {	
	scanf("%d%d", &n, &m), tot = 1;
	F(i, 1, m) {
		scanf("%d%d%d%d", &s, &t, &lower, &upper);
		d[s] += lower, d[t] -= lower, link(s, t, upper - lower);
		W[i] = tot; L[i] = lower;
	}
	ss = 0, tt = n + 1; int All = 0;
	F(i, 1, n)
		if (d[i] > 0) link(i, tt, d[i]); else link(ss, i, - d[i]), All += - d[i];
	int sum = 0;
	while (OK())
		mem(bz, 0), sum = sum + Dinic(ss, 1e9);
	if (sum < All)
		puts("NO");
	else {
		puts("YES");
		F(i, 1, m)
			printf("%d\n", L[i] + len[W[i]]);
	}
}
有源汇上下界可行流

在这个问题中,源和汇的流量无限只要求源的流出量等于汇的流入量,即可以看做源和汇不要求流量平衡

这让问题显得很难堪。

解决这个问题的思路是把有源汇变为无源汇。

其实很简单,只需让汇点连一条容量为无穷大的边到源点即可。

这样子,我们不需要讨论源点汇点是否满足流量平衡,而只需把整幅图看作一个无源汇的上下界网络流。

因为这个新图里,每个点都可以看做一个源,每个点可以看做一个汇,于是直接按照上面无源汇的做法去做即可。

有源汇上下界最大流/最小流

思路是一样的。

但一个很妙的思路是在残量网络上跑最大流。

因为残量网络已经保证下界限制,在这里继续跑最大流,当然是合法的。

所以最终最大流 = 可行流的流量 + 残量网络上最大流流量

如何算可行流的流量??实际上就是 t → s t\rightarrow s ts反向边流量。这是很好理解的。之所以不是 s s ss ss的流量的原因是显然的。只要你理解了为什么要连 t → s t\rightarrow s ts这条边即可。

在有源汇的图中,我们把每个点都看做了一个源,每个点都看做了一个汇。也就是说,可能不通过原图中的源 s s s,而直接从 s s ss ss出发,到达一个非源点,之后到达 t t tt tt。事实上,可以发现,如果除去 t → s t\rightarrow s ts这条边,原图中是不可能有第二条边连向 s s s的。

而因为这是一个无源汇的图,所以 s s s点的流出量就等于 t t t点的流入量, t t t点的流入量只能通过 t → s t\rightarrow s ts这一条边走到 s s s以此保证流量平衡。所以 s → t s\rightarrow t st的总流量就是 t → s t\rightarrow s ts的反向边流量。

而对于最小流的思路也是一样的,反过来跑,从 t t t s s s做最大流,然后减去即可。


最大流:

#include <bits/stdc++.h>

#define F(i,a,b) for (int i = a; i <= b; i ++)
#define mem(a, b) memset(a, b, sizeof a)
#define mec(a, b) memcpy(a, b, sizeof a)

const int inf = 1e9;
const int N = 300;
const int M = 4e4 + 10;

using namespace std;

int n, m, s, t, ss, tt, x, y, all, sum, lower, upper;
int dis[N], d[N], TOV[M], NEX[M], LAS[N], TOT;
int tov[M], nex[M], len[M], las[N], tot;

void link(int x, int y, int l) {
	tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = l;
	tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0;
}

bool OK(int ss, int tt) {
	queue <int> Q; Q.push(ss); mem(dis, 7); dis[ss] = 0;
	while (Q.size()) {
		int k = Q.front(); Q.pop();
		for (int x = las[k]; x ; x = nex[x])
			if (len[x] && dis[tov[x]] > dis[k] + 1) {
				dis[tov[x]] = dis[k] + 1;
				Q.push(tov[x]);
			}
	}
	return dis[tt] < 117901063;
}
int dinic(int k, int stop, int flow) {
	if (k == stop)
		return flow;
	int have = 0;
	for (int x = las[k]; x ; x = nex[x])
		if (len[x] && dis[tov[x]] == dis[k] + 1) {
			int now = dinic(tov[x], stop, min(flow - have, len[x]));
			have += now, len[x] -= now, len[x ^ 1] += now;
			if (flow == have)
				return have;
		}
	if (!have) dis[k] = - 1;
	return have;
}

int main() {
	scanf("%d%d%d%d", &n, &m, &s, &t), tot = 1;
	F(i, 1, m) {
		scanf("%d%d%d%d", &x, &y, &lower, &upper);
		d[x] -= lower, d[y] += lower;
		link(x, y, upper - lower);
	}
	mec(TOV, tov), mec(NEX, nex), mec(LAS, las);
	ss = 0, tt = n + 1;
	F(i, 1, n)
		if (d[i] > 0)
			all += d[i], link(ss, i, d[i]);
		else
			link(i, tt, - d[i]);
	link(t, s, inf);

 	while (OK(ss, tt))
		sum = sum + dinic(ss, tt, inf);

	if (all == sum) {
		sum = len[tot];
		mec(tov, TOV), mec(nex, NEX), mec(las, LAS);
		while (OK(s, t))
			sum = sum + dinic(s, t, inf);
		printf("%d\n", sum);
	}
	else
		puts("please go home to sleep");
}

最小流

#include <bits/stdc++.h>

#define I register int
#define F(i,a,b) for (I i = a; i <= b; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)
#define mec(a, b) memcpy(a, b, sizeof a)

const int inf = 1e9;
const int N = 5e4 + 20;
const int M = 4e5 + 10;

using namespace std;

int n, m, s, t, stop, ss, tt, x, y, all, sum, lower, upper;
int dis[N], d[N], TOV[M], NEX[M], LAS[N], TOT;
int tov[M], nex[M], len[M], las[N], cur[N], tot;

void link(I x, I y, I l) {
	tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = l;
	tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0;
}

bool OK(I ss, I tt) {
	queue <int> Q; Q.push(ss); mem(dis, - 1); dis[ss] = 0;
	while (Q.size()) {
		I k = Q.front(); Q.pop();
		for (I x = las[k]; x ; x = nex[x])
			if (len[x] && dis[tov[x]] == - 1) {
				dis[tov[x]] = dis[k] + 1;
				Q.push(tov[x]);
			}
	}
	return dis[tt] > - 1;
}
int dinic(I k, I flow) {
	if (k == stop || flow == 0)
		return flow;
	I have = 0;
	for (I &x = cur[k]; x ; x = nex[x]) {
		I y = tov[x];
		if (dis[y] == dis[k] + 1 && len[x]) {
			I now = dinic(y, min(flow - have, len[x]));
			have += now, len[x] -= now, len[x ^ 1] += now;
			if (flow == have)
				return have;
		}
	}
	if (!have)
		dis[k] = - 1;
	return have;
}
void Re(I &x) {
	char c = getchar(); x = 0;
	for (; !isdigit(c); c = getchar());
	for (; isdigit(c); x = (x << 3) + (x << 1) + c - '0', c = getchar());
}

int main() {	
	Re(n),Re(m),Re(s),Re(t), tot = 1;
	F(i, 1, m) {
		Re(x), Re(y), Re(lower), Re(upper);
		d[x] -= lower, d[y] += lower;
		link(x, y, upper - lower);
	}
	mec(TOV, tov), mec(NEX, nex), mec(LAS, las);
	ss = 0, tt = n + 1;
	F(i, 1, n)
		if (d[i] > 0)
			all += d[i], link(ss, i, d[i]);
		else
		if (d[i] < 0)
			link(i, tt, - d[i]);
	link(t, s, inf);

	stop = tt;
	while (OK(ss, tt)) {
		mec(cur, las);
		sum = sum + dinic(ss, inf);
	}
	if (all == sum) {
		sum = len[tot];
		mec(tov, TOV), mec(nex, NEX), mec(las, LAS); stop = s;
		while (OK(t, s)) {
			mec(cur, las);
			sum = sum - dinic(t, inf);
		}
		printf("%d\n", sum);
	}
	else
		puts("please go home to sleep");
}
费用流

在许多问题中,我们不仅想让流量最大,还想让每条边流量乘上其对应花费的总费用最小。

这样就有了费用流的问题。

我们有一种很暴力的思路,即每次找一条费用最小的边去增广,直到不能增广为止。因为是基于SPFA的,所以甚至可以存在负权费用。

这样的代码也很好打:

#include <bits/stdc++.h>

#define F(i,a,b) for (int i = a; i <= b; i ++)
#define mem(a, b) memset(a, b, sizeof a)

const int N = 400 + 10;
const int M = 3e4 + 10;

using namespace std;

int n, m, x, y, c, w, tot, s, t, Ans, Sum; bool vis[N];
int tov[M], nex[M], len[M], cost[M], las[N], dis[N], pre[N];

void ins(int x, int y, int c, int w) {
	tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = c, cost[tot] = w;
	tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0, cost[tot] = - w;
}

bool spfa() {
	queue <int> Q; Q.push(s); mem(vis, 0), vis[s] = 1; mem(dis, 7), dis[s] = 0;
	while (Q.size()) {
		int k = Q.front(); Q.pop();
		for (int x = las[k]; x ; x = nex[x])
			if (len[x] && dis[tov[x]] > dis[k] + cost[x]) {
				dis[tov[x]] = dis[k] + cost[x];
				pre[tov[x]] = x;
				if (!vis[tov[x]])
					Q.push(tov[x]), vis[tov[x]] = 1;
			}
		vis[k] = 0;
	}
	return dis[t] < 117901063;
}

int main() {
	scanf("%d%d", &n, &m), tot = 1, s = 1, t = n;
	F(i, 1, m) {
		scanf("%d%d%d%d", &x,&y,&c,&w);
		ins(x, y, c, w);
	}

	while (spfa()) {
		int Flow = 1e9;
		for (int i = t; pre[i]; i = tov[pre[i] ^ 1])
			Flow = min(Flow, len[pre[i]]);
		for (int i = t; pre[i]; i = tov[pre[i] ^ 1]) {
			Sum = Sum + Flow * cost[pre[i]];
			len[pre[i]] -= Flow, len[pre[i] ^ 1] += Flow;
		}
		Ans += Flow;
	}

	printf("%d %d\n", Ans, Sum);
}

注意到上面这种单路增广的方式实在是有点暴力。ZKW对此进行了优化,采取SPFA + 多路增广的方式。

注意多路增广时可能有负权圈的存在,所以要加上访问标记,即如果一个点已经访问过则不再访问。这样在保证每次能找到一条非零费用路径增广时,加快速度。

在稠密图上优势明显。

#include <bits/stdc++.h>

#define F(i,a,b) for (int i = a; i <= b; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)

const int N = 400 + 10;
const int M = 3e4 + 10;

using namespace std;

int n, m, x, y, c, w, tot, s, t, Ans, Sum; bool vis[N], bz[N];
int tov[M], nex[M], len[M], cost[M], las[N], dis[N];

void ins(int x, int y, int c, int w) {
	tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = c, cost[tot] = w;
	tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0, cost[tot] = - w;
}

bool spfa() {
	queue <int> Q; Q.push(s); mem(vis, 0), vis[s] = 1; mem(dis, 7), dis[s] = 0;
	while (Q.size()) {
		int k = Q.front(); Q.pop();
		for (int x = las[k]; x ; x = nex[x])
			if (len[x] && dis[tov[x]] > dis[k] + cost[x]) {
				dis[tov[x]] = dis[k] + cost[x];
				if (!vis[tov[x]])
					Q.push(tov[x]), vis[tov[x]] = 1;
			}
		vis[k] = 0;
	}
	return dis[t] < 117901063;
}

int dfs(int k, int flow) {
	vis[k] = 1;
	if (k == t) return flow;
	int have = 0;
	for (int x = las[k]; x ; x = nex[x])
		if (len[x] && !vis[tov[x]] && dis[k] + cost[x] == dis[tov[x]]) {
			int now = dfs(tov[x], min(flow - have, len[x]));
			if (now)
				Sum += now * cost[x], len[x] -= now, len[x ^ 1] += now, have += now;
			if (flow == have)
				return flow;
		}
	return have;
}

int main() {
	scanf("%d%d", &n, &m), tot = 1, s = 1, t = n;
	F(i, 1, m) {
		scanf("%d%d%d%d", &x,&y,&c,&w);
		ins(x, y, c, w);
	}

	while (spfa())
		for (vis[t] = 1; vis[t]; ) {
			mem(vis, 0);
			Ans += dfs(s, 1e9);
		}

	printf("%d %d\n", Ans, Sum);
}

ZKW在此基础上继续优化,加入spfa的SLF优化,继续优化时间。

#include <bits/stdc++.h>

#define F(i,a,b) for (int i = a; i <= b; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)

const int N = 400 + 10;
const int M = 3e4 + 10;

using namespace std;

int n, m, x, y, c, w, tot, s, t, Ans, Sum; bool vis[N], bz[N];
int tov[M], nex[M], len[M], cost[M], las[N], dis[N];

void ins(int x, int y, int c, int w) {
	tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = c, cost[tot] = w;
	tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0, cost[tot] = - w;
}

bool spfa() {
	deque <int> Q; Q.push_back(s); mem(vis, 0), vis[s] = 1; mem(dis, 7), dis[s] = 0;
	while (Q.size()) {
		int k = Q.front(); Q.pop_front();
		for (int x = las[k]; x ; x = nex[x])
			if (len[x] && dis[tov[x]] > dis[k] + cost[x]) {
				dis[tov[x]] = dis[k] + cost[x];
				if (!vis[tov[x]]) {
					vis[tov[x]] = 1;
					if (Q.size() && dis[tov[x]] < dis[Q.front()]) Q.push_front(tov[x]); else Q.push_back(tov[x]);
				}
			}
		vis[k] = 0;
	}
	return dis[t] < 117901063;
}

int dfs(int k, int flow) {
	vis[k] = 1;
	if (k == t) return flow;
	int have = 0;
	for (int x = las[k]; x ; x = nex[x])
		if (len[x] && !vis[tov[x]] && dis[k] + cost[x] == dis[tov[x]]) {
			int now = dfs(tov[x], min(flow - have, len[x]));
			if (now)
				Sum += now * cost[x], len[x] -= now, len[x ^ 1] += now, have += now;
			if (flow == have)
				return flow;
		}
	return have;
}

int main() {
	scanf("%d%d", &n, &m), tot = 1, s = 1, t = n;
	F(i, 1, m) {
		scanf("%d%d%d%d", &x,&y,&c,&w);
		ins(x, y, c, w);
	}

	while (spfa())
		for (vis[t] = 1; vis[t]; ) {
			mem(vis, 0);
			Ans += dfs(s, 1e9);
		}

	printf("%d %d\n", Ans, Sum);
}

以上就是费用流的几种实现方式。

上下界 + 费用流

终于来了。。。

如果在上下界的基础上加上费用流呢??

比如说:http://120.77.82.93/senior/#main/show/3302

由于费用不相同,所以不能按照上面的方法连边,但事实上有一种美妙的拆边方式:

即先建立源点汇点,超级源和超级汇。

然后尝试连边,每次把一条边 ( u , v , d w , u p , c o s t ) (u,v,dw,up,cost) (u,v,dw,up,cost)拆成三条 ( s s , v , d w , c o s t ) (ss,v,dw,cost) (ss,v,dw,cost) ( u , t t , d w , 0 ) (u,tt,dw,0) (u,tt,dw,0) ( u , v , u p − d w , c o s t ) (u,v,up-dw,cost) (u,v,updw,cost)

意思很好理解了。但如何保证下界呢?事实上方法与上面的一样,只要 s s ss ss的出边都流满即可。这样可以保证下界。这样就做完了。

但是对于这道题有一个二次方的,所以要动态加边,而且要拆系数,维护。所以只能打EK,不能用ZKW。

#include <bits/stdc++.h>

#define F(i,a,b) for (int i=a;i<=b;i++)
#define mem(a,b) memset(a,b,sizeof a)

const int N=2e2+10;
const int M=1e5+10;
const int inf=1e9;

using namespace std;

int n,m,lef,in,out, sum,u,v,a,b,L,U; bool vis[N];
int s,t,ss,tt,tot, shu[M],tov[M],nex[M],len[M],cost[M],pre[M],lv[M],rv[M],las[N],dis[N];
struct yls { int u,v,L,U,a,b; } jl[M];

void ins(int x,int y,int l,int w){
	if (l==0) return;
	tov[++tot]=y,nex[tot]=las[x],las[x]=tot,len[tot]=l,cost[tot]= w;
	tov[++tot]=x,nex[tot]=las[y],las[y]=tot,len[tot]=0,cost[tot]=-w;
}
void link(int x,int y,int dw,int up,int w){
	ins(x,y,up-dw,w);
	ins(ss,y,dw,w);
	ins(x,tt,dw,0);
}

bool spfa(int s,int t) {
	deque<int> Q; Q.push_front(s); mem(vis, 0), vis[s] = 1, mem(dis, 7), dis[s] = 0;
	while (Q.size()) {
		int k = Q.front(); Q.pop_front();
		for (int x=las[k];x;x=nex[x])
			if (len[x]&&dis[tov[x]]>dis[k]+cost[x]) {
				dis[tov[x]]=dis[k]+cost[x];
				pre[tov[x]] = x;
				if (vis[tov[x]]) continue;
				vis[tov[x]] = 1;
				if (Q.size() && dis[tov[x]] < dis[Q.front()]) Q.push_front(tov[x]); else Q.push_back(tov[x]);
			}
		vis[k]=0;
	}
	return dis[t]<117901063;
}

int main(){
	scanf("%d%d", &n,&m), s=0,t=n+1, ss=n+2,tt=n+3, tot=1;
	F(i,1,n){
		scanf("%d%d%d", &lef,&in,&out);
		if (lef>0) link(s,i,lef,lef,0);
		if (lef<0) link(i,t,-lef,-lef,0);
		link(s,i,0,inf,in);link(i,t,0,inf,out);
	}
	F(i,1,m){
		scanf("%d%d%d%d%d%d",&u,&v,&a,&b,&L,&U);
		ins(ss,v,L,L*a+b);
		ins(u,tt,L,0);
		if (L==U) continue;
		ins(u,v,1,(2*L+1)*a+b);
		lv[i]=rv[i]=1;
		jl[i]={u,v,L,U,a,b};
		shu[tot-1]=shu[tot]=i;
	}
	ins(t,s,inf,0); int Ans = 0;
	while (spfa(ss,tt)) {
		int Flow=inf;
		for (int i=tt; pre[i]; i=tov[pre[i]^1])
			Flow=min(Flow,len[pre[i]]);
		for (int i=tt,j; pre[i]; i=tov[j^1]){
			j=pre[i];
			int y=shu[j];
			sum=sum+cost[j]*Flow;
			len[j] -= Flow, len[j ^ 1] += Flow;
			if (!y) continue;
			if (cost[j]>0) {
				lv[y]++;
				if (lv[y]>rv[y]) {
					rv[y]=lv[y];
					u=jl[y].u,v=jl[y].v,L=jl[y].L,U=jl[y].U,a=jl[y].a,b=jl[y].b;
					if (rv[y]>U-L) continue;
					ins(u,v,1,(2*L+2*lv[y]-1)*a+b);
					shu[tot-1]=shu[tot]=y;
					
				}
			} else lv[y]--;
		}
		Ans += Flow;
	}
	printf("%d\n",sum);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值