1. 基础概念
1.1 传统网络流 vs 上下界网络流
传统网络流(最大流问题):
- 每条边
(u, v)只有容量上限c(u, v) - 流量约束:
0 ≤ f(u, v) ≤ c(u, v) - 满足流量守恒:对除源点和汇点外的任意点
u,有∑f(·, u) = ∑f(u, ·)
上下界网络流:
- 每条边
(u, v)有下界l(u, v)和上界c(u, v) - 流量约束:
l(u, v) ≤ f(u, v) ≤ c(u, v) - 同样满足流量守恒
1.2 为什么需要上下界?
在实际应用中,很多场景不仅有容量限制,还有最低要求:
- 生产系统:每条生产线有最低产量要求
- 交通网络:某些道路必须保持最低车流量
- 电力系统:输电线路有最小功率传输要求
- 资源分配:每个任务需要的资源有最低保障
2. 无源汇上下界可行流
这是最基础的上下界网络流问题,也是解决其他问题的基础。
2.1 问题描述
给定一个有向图,每条边有流量下界和上界,问是否存在一个循环流(即每个点都满足流量守恒)满足所有约束。
2.2 核心思想:强制流量分离
我们将每条边的流量分解为两部分:
- 强制流量:等于下界
l(u, v),这部分必须存在 - 自由流量:在
[0, c(u, v) - l(u, v)]范围内可变
2.3 详细算法步骤
步骤1:计算每个点的"不平衡量"
对每个点 u,计算:
d[u] = ∑l(·, u) - ∑l(u, ·)
其中:
∑l(·, u):所有指向u的边的下界之和∑l(u, ·):所有从u出发的边的下界之和
d[u] 的含义:
d[u] > 0:点u的流入强制流量大于流出强制流量,需要额外d[u]的流入来平衡d[u] < 0:点u的流出强制流量大于流入强制流量,需要额外|d[u]|的流出d[u] = 0:强制流量已平衡
步骤2:构建新网络
创建超级源点 S 和超级汇点 T:
- 对每个满足
d[u] > 0的点u,添加边(S, u),容量为d[u] - 对每个满足
d[u] < 0的点u,添加边(u, T),容量为-d[u] - 原图中每条边
(u, v)的容量变为c(u, v) - l(u, v)(去掉下界部分)
步骤3:求解最大流
在新网络上求 S 到 T 的最大流。
步骤4:判断可行性
如果最大流等于 ∑d[u](对所有 d[u] > 0 的点求和),则存在可行流。
2.4 为什么这个方法正确?
必要性证明:
如果存在可行流,那么:
- 所有
d[u] > 0的点需要的额外流入总量必须等于所有d[u] < 0的点需要的额外流出总量 - 这个总量就是
∑d[u] (d[u] > 0) - 因此从
S流出的流量必须等于这个值
充分性证明:
如果最大流等于 ∑d[u] (d[u] > 0),说明:
- 所有点的不平衡量都被补偿
- 将最大流的流量加上强制流量,就得到原图的可行流
2.5 示例分析
考虑以下网络(边标注为 l/c,表示下界/上界):
1/3 1/4
1 ------> 2 ------> 3
| ^ |
| 1/2 | 1/2 | 1/3
| | |
V | V
4 ------> 5 ------> 6
1/3 1/4
步骤1:计算不平衡量
| 点 | ∑l(·,u) | ∑l(u,·) | d[u] |
|---|---|---|---|
| 1 | 0 | 1+1=2 | -2 |
| 2 | 1+1=2 | 1=1 | 1 |
| 3 | 1 | 0 | 1 |
| 4 | 1 | 1 | 0 |
| 5 | 1+1=2 | 1+1=2 | 0 |
| 6 | 1 | 0 | 1 |
步骤2:构建新网络
- 添加边
(S, 2),容量 1 - 添加边
(S, 3),容量 1 - 添加边
(S, 6),容量 1 - 添加边
(1, T),容量 2 - 修改原边容量:所有边容量减1
步骤3:求最大流
在新网络上求 S 到 T 的最大流,如果等于 3(1+1+1),则存在可行流。
3. 有源汇上下界可行流
3.1 问题描述
给定源点 s 和汇点 t,问是否存在从 s 到 t 的可行流。
3.2 转化方法
核心思想: 将有源汇问题转化为无源汇问题。
步骤:
- 添加一条从汇点
t到源点s的边,下界为 0,上界为无穷大 - 转化为无源汇问题求解
- 如果存在可行流,则原问题存在可行流
3.3 为什么这样转化?
添加 t → s 的边后:
- 整个网络变成了循环流
- 从
s到t的流量等于从t到s的流量 - 这正是我们想要的流
4. 有源汇上下界最大流
4.1 问题描述
在满足上下界约束的前提下,求从源点到汇点的最大流量。
4.2 两步法
步骤1:求可行流
使用上述方法求一个可行流。
步骤2:在残余网络中求最大流
- 保持可行流的状态
- 在残余网络中,从源点
s到汇点t求最大流 - 总流量 = 可行流中的流量 + 第二步求得的最大流
4.3 详细解释
为什么可以在残余网络中继续增广?
- 可行流已经满足所有上下界约束
- 在残余网络中增广不会破坏下界约束
- 因为增广只会增加流量,而不会减少到下界以下
5. 有源汇上下界最小流
5.1 问题描述
在满足上下界约束的前提下,求从源点到汇点的最小流量。
5.2 两步法
步骤1:求可行流
同最大流的第一步。
步骤2:尝试减少流量
- 在残余网络中,从汇点
t到源点s求最大流(反向增广) - 最小流量 = 可行流中的流量 - 第二步求得的最大流
5.3 为什么可以反向增广?
反向增广相当于:
- 减少从
s到t的流量 - 只要不减少到下界以下,就是合法的
- 残余网络中的反向边容量保证了不会违反下界约束
6. C++ 实现(超详细注释版)
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 210; // 最大点数(考虑超级源汇)
const int INF = 0x3f3f3f3f; // 无穷大,使用0x3f3f3f3f避免溢出
// 边的结构体
struct Edge {
int to; // 目标节点
int rev; // 反向边在邻接表中的索引
int cap; // 容量
int flow; // 当前流量
int lower; // 下界(原始下界,用于恢复实际流量)
// 构造函数
Edge(int t, int r, int c, int l = 0) : to(t), rev(r), cap(c), flow(0), lower(l) {}
};
class LowerBoundFlow {
private:
vector<Edge> graph[MAXN]; // 邻接表表示图
int level[MAXN]; // BFS分层
int iter[MAXN]; // 当前弧优化
int n, s, t; // 原图的点数、源点、汇点
int S, T; // 超级源点、超级汇点
// 内部添加边函数(不包含下界信息)
void add_edge_internal(int from, int to, int cap, int lower = 0) {
graph[from].push_back(Edge(to, graph[to].size(), cap, lower));
// 添加反向边,容量为0
graph[to].push_back(Edge(from, graph[from].size() - 1, 0, 0));
}
// BFS构建分层图
void bfs(int s) {
// 初始化所有点的层级为-1(未访问)
memset(level, -1, sizeof(level));
queue<int> q;
level[s] = 0;
q.push(s);
while (!q.empty()) {
int u = q.front(); q.pop();
// 遍历u的所有出边
for (const Edge& e : graph[u]) {
// 如果边还有剩余容量且目标点未访问
if (e.cap > e.flow && level[e.to] < 0) {
level[e.to] = level[u] + 1;
q.push(e.to);
}
}
}
}
// DFS寻找增广路
int dfs(int u, int t, int f) {
if (u == t) return f; // 到达汇点,返回可增广流量
// 当前弧优化:从上次中断的位置开始
for (int& i = iter[u]; i < graph[u].size(); i++) {
Edge& e = graph[u][i];
// 如果边有剩余容量且目标点在下一层
if (e.cap > e.flow && level[u] < level[e.to]) {
// 递归寻找增广路
int d = dfs(e.to, t, min(f, e.cap - e.flow));
if (d > 0) {
// 更新正向边流量
e.flow += d;
// 更新反向边流量(反向边流量减少)
graph[e.to][e.rev].flow -= d;
return d;
}
}
}
return 0; // 未找到增广路
}
// Dinic算法求最大流
int max_flow(int s, int t) {
int total_flow = 0;
while (true) {
// 构建分层图
bfs(s);
// 如果无法到达汇点,结束
if (level[t] < 0) return total_flow;
// 当前弧优化初始化
memset(iter, 0, sizeof(iter));
int f;
// 不断寻找增广路
while ((f = dfs(s, t, INF)) > 0) {
total_flow += f;
}
}
}
// 清除指定点的流量(用于最小流计算)
void clear_flow(int u) {
for (Edge& e : graph[u]) {
e.flow = 0;
}
}
public:
// 构造函数
LowerBoundFlow(int nodes, int source, int sink) : n(nodes), s(source), t(sink) {
S = n + 1; // 超级源点编号
T = n + 2; // 超级汇点编号
// 清空图
for (int i = 0; i <= n + 2; i++) {
graph[i].clear();
}
}
// 添加带上下界的边
void add_edge(int from, int to, int lower, int upper) {
// 实际添加的边容量为 upper - lower
add_edge_internal(from, to, upper - lower, lower);
}
// 求无源汇上下界可行流
bool feasible_flow() {
vector<int> demand(n + 1, 0); // 每个点的需求量
// 计算每个点的净需求
for (int u = 1; u <= n; u++) {
for (const Edge& e : graph[u]) {
if (e.lower > 0) { // 只考虑原始的带下界边
// 流出u的强制流量
demand[u] -= e.lower;
// 流入v的强制流量
demand[e.to] += e.lower;
}
}
}
// 统计总需求
int total_demand = 0;
// 添加超级源点和超级汇点的边
for (int u = 1; u <= n; u++) {
if (demand[u] > 0) {
// 需要额外流入,从超级源点连入
add_edge_internal(S, u, demand[u]);
total_demand += demand[u];
} else if (demand[u] < 0) {
// 需要额外流出,连向超级汇点
add_edge_internal(u, T, -demand[u]);
}
}
// 如果总需求为0,直接存在可行流
if (total_demand == 0) return true;
// 求超级源点到超级汇点的最大流
int flow = max_flow(S, T);
// 如果最大流等于总需求,说明可以满足所有点的需求
return flow == total_demand;
}
// 求有源汇上下界可行流
bool feasible_flow_st() {
// 添加t->s的无穷边,将有源汇转化为无源汇
add_edge_internal(t, s, INF);
// 求解无源汇可行流
return feasible_flow();
}
// 求有源汇上下界最大流
int max_flow_st() {
// 先求可行流
if (!feasible_flow_st()) {
return -1; // 不存在可行流
}
// 保存可行流中t->s边的流量(即基础流量)
int base_flow = 0;
for (const Edge& e : graph[t]) {
if (e.to == s) {
base_flow = e.flow;
break;
}
}
// 重置流量,准备在残余网络中求最大流
// 注意:只重置原图中的边,保留t->s边的流量
for (int u = 1; u <= n; u++) {
for (Edge& e : graph[u]) {
if (!(u == t && e.to == s)) { // 不是t->s边
e.flow = 0;
}
}
}
// 在残余网络中求s->t的最大流
int additional_flow = max_flow(s, t);
// 总流量 = 基础流量 + 额外流量
return base_flow + additional_flow;
}
// 求有源汇上下界最小流
int min_flow_st() {
// 先求可行流
if (!feasible_flow_st()) {
return -1; // 不存在可行流
}
// 保存可行流中t->s边的流量(即基础流量)
int base_flow = 0;
for (const Edge& e : graph[t]) {
if (e.to == s) {
base_flow = e.flow;
break;
}
}
// 重置流量
for (int u = 1; u <= n; u++) {
for (Edge& e : graph[u]) {
if (!(u == t && e.to == s)) {
e.flow = 0;
}
}
}
// 在残余网络中求t->s的最大流(反向增广)
// 这相当于减少从s到t的流量
int reduce_flow = max_flow(t, s);
// 最小流量 = 基础流量 - 可减少的流量
return base_flow - reduce_flow;
}
// 获取实际流量(用于调试和验证)
void print_actual_flow() {
cout << "实际流量分布:" << endl;
for (int u = 1; u <= n; u++) {
for (const Edge& e : graph[u]) {
if (e.lower > 0) { // 原始的带下界边
int actual_flow = e.flow + e.lower;
cout << "边 " << u << " -> " << e.to
<< ": 流量 = " << actual_flow
<< " (下界=" << e.lower
<< ", 上界=" << (e.lower + e.cap) << ")" << endl;
}
}
}
}
// 验证流量是否满足约束(调试用)
bool verify_flow() {
// 检查每条边的流量是否在上下界内
for (int u = 1; u <= n; u++) {
for (const Edge& e : graph[u]) {
if (e.lower > 0) { // 原始边
int actual_flow = e.flow + e.lower;
if (actual_flow < e.lower || actual_flow > e.lower + e.cap) {
return false;
}
}
}
}
return true;
}
};
// 使用示例和测试
int main() {
cout << "=== 上下界网络流算法演示 ===" << endl;
// 示例1:有源汇上下界最大流
cout << "\n示例1:有源汇上下界最大流" << endl;
LowerBoundFlow flow1(4, 1, 4); // 4个点,源点1,汇点4
// 添加边:from, to, lower, upper
flow1.add_edge(1, 2, 1, 3);
flow1.add_edge(1, 3, 1, 2);
flow1.add_edge(2, 3, 1, 2);
flow1.add_edge(2, 4, 1, 3);
flow1.add_edge(3, 4, 1, 2);
int max_flow = flow1.max_flow_st();
if (max_flow == -1) {
cout << "不存在可行流!" << endl;
} else {
cout << "最大流: " << max_flow << endl;
flow1.print_actual_flow();
cout << "流量验证: " << (flow1.verify_flow() ? "通过" : "失败") << endl;
}
// 示例2:无源汇上下界可行流
cout << "\n示例2:无源汇上下界可行流" << endl;
LowerBoundFlow flow2(3, 1, 3); // 用于无源汇问题,s,t参数不重要
flow2.add_edge(1, 2, 1, 3);
flow2.add_edge(2, 3, 1, 3);
flow2.add_edge(3, 1, 1, 3);
bool feasible = flow2.feasible_flow();
cout << "是否存在可行流: " << (feasible ? "是" : "否") << endl;
return 0;
}
7. 算法正确性深入分析
7.1 可行性判定的数学证明
设原图为 G = (V, E),其中每条边 e ∈ E 有下界 l(e) 和上界 c(e)。
定义:
f: E → R为流量函数- 对每个点
v ∈ V,定义净流量:div(f, v) = ∑f(·, v) - ∑f(v, ·)
对于可行流,要求:
l(e) ≤ f(e) ≤ c(e),对所有e ∈ Ediv(f, v) = 0,对所有v ∈ V(无源汇情况)
引理1:如果存在可行流 f,则对任意点集 U ⊂ V,有:
∑_{e ∈ δ⁻(U)} l(e) ≤ ∑_{e ∈ δ⁺(U)} c(e)
其中 δ⁻(U) 是进入 U 的边集,δ⁺(U) 是离开 U 的边集。
这个引理说明了可行流存在的必要条件。
引理2:通过超级源汇构造的新网络中,最大流等于 ∑d[v]⁺ 当且仅当原网络存在可行流。
这个引理证明了算法的充分必要性。
7.2 复杂度优化技巧
- 当前弧优化:避免重复检查已饱和的边
- 多路增广:一次DFS中尽可能多地增广
- ISAP算法:可以进一步优化,避免重复BFS
8. 实际应用案例
8.1 生产调度问题
某工厂有3条生产线,生产2种产品:
- 生产线1:产能
[10, 50],可生产产品A - 生产线2:产能
[15, 60],可生产产品B - 生产线3:产能
[20, 80],可生产产品A和B
产品需求:
- 产品A:至少需要40单位
- 产品B:至少需要35单位
建模:
- 源点 → 生产线:下界=最小产能,上界=最大产能
- 生产线 → 产品:无下界,上界=生产线产能
- 产品 → 汇点:下界=需求,上界=无穷大
8.2 交通网络优化
城市道路网络中:
- 主干道:车流量必须在
[1000, 5000]辆/小时 - 次干道:车流量必须在
[500, 3000]辆/小时 - 问:能否在满足这些约束的情况下,实现从A区到B区的最大交通流量?
9. 常见错误和陷阱
- 无穷大值选择不当:导致整数溢出
- 忘记重置流量:在求最大/最小流时未正确处理残余网络
- 下界为0的边处理:需要特殊判断
- 点编号冲突:超级源汇编号与原图冲突
- 流量恢复错误:实际流量 = 流量 + 下界
10. 总结
上下界网络流是网络流理论的重要扩展,其核心思想是:
- 分离强制流量和自由流量
- 通过超级源汇补偿不平衡
- 利用残余网络进行优化
掌握这一技术需要理解:
- 流量守恒的本质
- 网络流的线性规划本质
- 转化思想的应用
上下界网络流算法详解与应用
5049

被折叠的 条评论
为什么被折叠?



