上下界网络流

创建时间:2025-08-13


无源汇可行流

无源汇可行流,即没有源点和汇点的网络,每条边有一个流量上限和一个流量下限,需要判断是否存在一个满足上下界限制的方案。

不妨先将所有边的流量均设为下限,如果此时满足流量守恒,这个问题已经结束了。然而,流量守恒不一定成立,即还要想办法让这个网络变得平衡。

在平衡网络的过程中,不妨将下限为 \(low\) 上限为 \(upp\) 的边的上限设为是 \(upp-low\),下限设为 \(0\)。这样上下限的限制自然满足。

\(in_i\) 为流入 \(i\) 的边的下限和,\(out_i\) 流出 \(i\) 的边的下限和。根据流量守恒,当 \(in_i>out_i\) 时,\(i\) 的部分流量必须分给其他边,且 \(i\) 没必要再接收多余的流量,考虑超级源点向 \(i\) 连边;当 \(in_i\le out_i\) 时,\(i\) 必须接收其他边流入的流量,且 \(i\) 没必要再分出多余流量,考虑将 \(i\) 向超级汇点连边。如果最后网络的最大流等于 \(\sum_{in_i>out_i}( in_i-out_i)/\sum_{in_i\le out_i} (out_i-in_i)\) 的话,说明平衡成功,则有解,否则无解。

代码:

#include <bits/stdc++.h>

using namespace std;

#define int long long

const int INF = 2e9;
const int MAX_N = 250;
const int MAX_M = 10500;

int n, m, s, t, low[MAX_M], upp[MAX_M];
int in[MAX_N], out[MAX_N], dep[MAX_N];
int cnt = 1, head[MAX_N], now[MAX_N];
struct Edge {
	int v, w, nxt;
} e[MAX_M * 2];

void add_edge(int u, int v, int w) {
	e[++cnt] = Edge{v, w, head[u]};
	head[u] = cnt;
	e[++cnt] = Edge{u, 0, head[v]};
	head[v] = cnt;
}

bool bfs() {
	memset(dep, 0, sizeof(dep));
	memcpy(now, head, sizeof(now)); 
	queue<int> q;
	dep[s] = 1;
	q.push(s);
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		for (int i = head[u]; i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].w && !dep[v]) {
				dep[v] = dep[u] + 1;
				q.push(v);
			}
		}
	} 
	return dep[t];
}

int dfs(int u, int sum) {
	if (u == t)
		return sum;
	int flow = 0;
	for (int i = now[u]; i && sum; i = e[i].nxt) {
		now[u] = i;
		int v = e[i].v;
		if (e[i].w && dep[v] == dep[u] + 1) {
			int tmp = dfs(v, min(sum, e[i].w));
			if (!tmp)
				dep[v] = 0;
			e[i].w -= tmp, e[i ^ 1].w += tmp;
			sum -= tmp, flow += tmp;
		}
	}
	return flow;
}

signed main() {
	cin >> n >> m;
	for (int i = 1, u, v; i <= m; i++) {
		cin >> u >> v >> low[i] >> upp[i];
		in[v] += low[i], out[u] += low[i];
		add_edge(u, v, upp[i] - low[i]);
	}
	s = n + 1, t = n + 2;
	int sum = 0, maxflow = 0;
	for (int i = 1; i <= n; i++)
		if (in[i] > out[i]) {
			sum += in[i] - out[i];
			add_edge(s, i, in[i] - out[i]);
		} else 
			add_edge(i, t, out[i] - in[i]);
	while (bfs())
		maxflow += dfs(s, INF);
	if (maxflow != sum) {
		cout << "NO\n";
		return 0;
	}
	cout << "YES\n";
	for (int i = 1; i <= m; i++)
		cout << e[i * 2 + 1].w + low[i] << '\n';
	return 0;
}

有源汇上下界最大(小)流

首先考虑有源汇上下界最大流。

为了将模型转化,考虑将源点和汇点变成普通的点,即向其他,并用超级源点和超级汇点作为网络的源点和汇点。转化的方法也很简单,直接从原汇点向原源点连一条边权位 \(\infty\) 的边即可。这样原源点流到原汇点的流量可以通过这条边流回来,原源点和原汇点自然变成了两个普通的点。

这样就可以通过无源汇上下界可行流的方法判定是否有解了。接下来就是怎么求最大流了。

无源汇上下界可行流跑完后的原源点到原汇点的流量显然就是那条原汇点到原源点的边的流量,也即其反向边的边权,这是不超过要求的最大流的。由于之前求的最大流实际上是超级源点到超级汇点的最大流,这并不一定满足原源点到原汇点的最大流,故还需要在补充一些流量。考虑记下当前原源点到原汇点的流量,在残余网络上把原源点到原汇点的边与其反向边断掉,然后在接着以原源点为源点,原汇点为汇点跑最大流,得到的答案加上之前的边权就是要求的最大流。

残余网络的建图方式保证了这个算法的正确性,在残余网络上重跑最大流保证了算法的最优性。

代码和之前相似,只要改一下主函数即可。

signed main() {
	cin >> n >> m >> s >> t;
	for (int i = 1, u, v; i <= m; i++) {
		cin >> u >> v >> low[i] >> upp[i];
		in[v] += low[i], out[u] += low[i];
		add_edge(u, v, upp[i] - low[i]);
	}
	s0 = n + 1, t0 = n + 2;
	int sum = 0, maxflow = 0;
	for (int i = 1; i <= n; i++)
		if (in[i] > out[i]) {
			sum += in[i] - out[i];
			add_edge(s0, i, in[i] - out[i]);
		} else 
			add_edge(i, t0, out[i] - in[i]);
	add_edge(t, s, INF);
	while (bfs())
		maxflow += dfs(s0, INF);
	if (maxflow != sum) {
		cout << "please go home to sleep\n";
		return 0;
	}
	maxflow = e[cnt].w;
	e[cnt].w = e[cnt ^ 1].w = 0;
	s0 = s, t0 = t;
	while (bfs())
		maxflow += dfs(s0, INF);
	cout << maxflow << '\n';
	return 0;
}

类比有源汇上下界最大流求解有源汇上下界最小流,也将原源点和原汇点变成普通的点,还是先跑无源汇可行流,判断是否有解,再在残余网络上断掉原源点和原汇点的边并操作。

之前的最大流部分原源点到原汇点的流量是需要补充才能得到最终答案的,最小流时反之,需要撤回一些流量。撤回流量也很简单,将其视为从原汇点还一些流量给原源点,将部分流量推回去,即从原汇点到原源点跑最大流,将答案减去它即可。

代码:

signed main() {
	cin >> n >> m >> s >> t;
	for (int i = 1, u, v; i <= m; i++) {
		cin >> u >> v >> low[i] >> upp[i];
		in[v] += low[i], out[u] += low[i];
		add_edge(u, v, upp[i] - low[i]);
	}
	s0 = n + 1, t0 = n + 2;
	int sum = 0, maxflow = 0;
	for (int i = 1; i <= n; i++)
		if (in[i] > out[i]) {
			sum += in[i] - out[i];
			add_edge(s0, i, in[i] - out[i]);
		} else 
			add_edge(i, t0, out[i] - in[i]);
	add_edge(t, s, INF);
	while (bfs())
		maxflow += dfs(s0, INF);
	if (maxflow != sum) {
		cout << "please go home to sleep\n";
		return 0;
	}
	maxflow = e[cnt].w;
	e[cnt].w = e[cnt ^ 1].w = 0;
	s0 = t, t0 = s;
	while (bfs())
		maxflow -= dfs(s0, INF);
	cout << maxflow << '\n';
	return 0;
}

无(有)源汇上下界最小费用可行流

有了之前的推导,无(有)源汇上下界最小费用可行流的问题就变得很简单了,直接在可行流的基础上给原图中存在的边赋上权值,将最大流改为最小费用最大流就行了。不过在这之前,还得先将答案加上 \(\sum low_icost_i\),即将网络填充至每条边的流量恰好为下限的代价。

代码(无源汇上下界最小费用可行流)
代码(有源汇上下界最小费用可行流)

上下界网络流的例题

P2304 [NOI2015] 小园丁与老司机(DP+有源汇上下界最小流)
P4043 [AHOI2014/JSOI2014] 支线剧情(有源汇上下界最小费用可行流)
P3980 [NOI2008] 志愿者招募(无源汇上下界最小费用可行流)

posted @ 2026-06-02 16:19  xubaichuan  阅读(10)  评论(0)    收藏  举报