网络流(最大流)
网络流(最大流)
1.1基本概念
在一个有向图上选择一个源点,一个汇点,每一条边上都有一个流量上限(以下称为容量),即经过这条边的流量不能超过这个上界,同时,除源点和汇点外,所有点的入流和出流都相等,而源点只有流出的流,汇点只有汇入的流。这样的图叫做网络流。
1.2.1 条件限制
- 容量限制:对于任意u,v,都有f(u,v) <= c(u,v)。即任意时刻流量都不超过容量;
- 流守恒性:除了源点与汇点之外,其余任意一点,都满足流入该点总量 = 流出该点总量,节点自身不会产生或者消耗流量,只是中转媒介;
- 反对称性:对于任意的u,v ,一定有f(u,v) = -f(v,u) 。 从u到v流了 l 流量就相当于从v到u流了-l 的流量;
1.2.2 可行流的流量
指从源点流出的流量 - 流入源点的流量
1.2.3 最大流
对于网络 ( G = (V, E) ),给每条边指定流量,得到合适的流 ( f ),使得 ( f ) 的流量尽可能大。此时我们称 ( f ) 是 ( G ) 的最大流。
1.2.4 增广路径
如果残留网中一直选择边权大于0的边走可以从源点走到汇点那么就存在一条增广路
残量网络
- 残量网络的可行流 + 原网络的可行流 = 原问题的另一个可行流
最小割问题
对于网络 ( G = (V, E) ),找到合适的 ( s-t ) 割 ({S, T}),使得 ({S, T}) 的总容量尽可能小。此时我们称 ({S, T}) 的总容量是 ( G ) 的最小割。
最小费用最大流问题
在网络 ( G = (V, E) ) 上,对每条边给定一个权值 ( w(u, v) ),称为费用(cost),含义是单位流量通过 ((u, v)) 所花费的代价。对于 ( G ) 所有可能的最大流,我们称其中总费用最小的一者为最小费用最大流。
最大流最小割定理
对于任意割 [S,T],|f| <= c(S,T)
以下三个条件等价:
- 流 f 是最大流
- 流 f 的残留网络中不存在增广路
- 存在某个割 [S,T],使得 |f| = c(S,T)
求解算法
Edmonds-Karp (EK) 算法
一、算法概述
核心思想:采用BFS寻找增广路径,保证找到最短增广路
主要特点:
- 属于增广路算法家族
- 时间复杂度::O(VE²)
- 适合稀疏图的中小规模问题
二、算法原理
2.1 残量网络定义
对于流网络 G=(V,E) 和流 f,残量网络 G_f 满足:
2.2 关键操作
-
BFS寻路:每次找最短的s→t路径
-
瓶颈值计算:
\[\ b = \min\{c_f(u,v) \mid (u,v)\in p\} \] -
增广操作:沿路径增减流量
Dinic 算法
一、详解
Dinic算法总是寻找最短的增广路,并沿着它增广。因为最短增广路的长度在增广过程中始终不会变短,所以无需每次都通过深度预先搜索来寻找最短增广路,我们可以先进行一次宽度优先搜索,然后考虑由近距离顶点指向远距离顶点的边所组成的分层图,在上面进行深度优先搜索寻找最短增广路。如果在分层图上找不到新的增广路了,则说明最短增广路的长度确实变长了,或不存在增广路了,于是重新通过宽度优先搜索构造新的分层图。此外,还可以对这个算法进行优化。
当前弧优化:在每次对分层图进行深度优先搜索寻找增广路时,避免对一条没有用的边进行多次检查。
优势:比EK更高效的最大流算法,时间复杂度 O(V²E)
二、核心思想
双重优化:
- 分层图:通过BFS建立层次结构
- **阻塞流:在层次图上用DFS找增广路
ISAP算法
它是Dinic算法的改进版本
内容
- 层次图:与Dinic算法类似,ISAP也使用层次图的概念,即从源点出发按照BFS分层。
- 反向BFS:ISAP的一个关键改进是从汇点开始进行反向BFS来初始化距离标号,而不是每次阻塞流后重新BFS。
- 间隙优化:当某个距离值没有节点时,可以确定已经找到最大流。
- 时间复杂度:O(V²E),与Dinic算法相同,但实际运行效率通常更高
- 空间复杂度:O(V+E)
HLLP(预流推进)
核心思想:通过局部操作(推进+重标记)而非全局增广路来求解最大流
内容
- 预流(Preflow):允许节点的流入量暂时大于流出量(即存在"超额流")。
- 高度标号:为每个节点分配高度,流只能从高节点推向低节点。
- 最高标号优先:总是处理当前高度最高的超额节点,减少无效操作。
- **时间复杂度 O(V²√E)
初始化
- 高度标号:
- 源点高度设为
h[s] = V(节点总数),汇点h[t] = 0。 - 其他节点
h[u] = 0。
- 源点高度设为
- 预流:
- 从源点
s向所有相邻节点推流,使其达到饱和(即边容量被完全利用)。 - 此时源点可能仍有超额流,其他节点开始积累超额流。
- 从源点
2. 推进(Push)操作
对当前最高标号的超额节点 u,尝试将超额流推向邻居 v:
- 条件:
h[u] = h[v] + 1(满足高度约束)。- 边
(u,v)有剩余容量c(u,v) > 0。
- 操作:
- 推送量
Δ = min(d[u], c(u,v))。 - 更新残余网络:
c(u,v) -= Δ,c(v,u) += Δ。 - 更新超额流:
d[u] -= Δ,d[v] += Δ。
- 推送量
3. 重标号(Relabel)操作
当节点 u 有超额流但无法推送时:
- 将其高度提升为
h[u] = min{h[v]} + 1(v为u的邻居且边有剩余容量)。 - 保证后续能继续推流。
4. 最高标号优先策略
- 使用一个桶(Bucket)或优先队列维护当前所有超额节点,按高度从高到低处理。
- 避免低标号节点的无效操作,加速收敛。
5. 终止条件
- 当除源点
s和汇点t外,所有节点的超额流为0。 - 此时汇点
t的流入量即为最大流。
Template
EK算法
template <typename T>
struct EK_ {
const int n;
const T inf = std::numeric_limits<T>::max();
struct Edge {
int to;
T w;
Edge(int to, T w) : to(to), w(w) {}
};
std::vector<Edge> ver;
std::vector<std::vector<int>> h;
std::vector<int> pre;
EK_(int n) : n(n + 1), h(n + 1) {}
void add(int u, int v, T c) {
h[u].push_back((int)ver.size());
ver.emplace_back(v, c);
h[v].push_back((int)ver.size());
ver.emplace_back(u, 0);
}
bool bfs(int s, int t) {
pre.assign(n, -1);
std::queue<int> q;
q.push(s);
pre[s] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int id : h[u]) {
int v = ver[id].to;
if (pre[v] == -1 && ver[id].w > 0) {
pre[v] = id;
if (v == t) return true;
q.push(v);
}
}
}
return false;
}
T work(int s, int t) {
T max_flow = 0;
while (bfs(s, t)) {
T min_flow = inf;
for (int v = t; v != s; v = ver[pre[v] ^ 1].to) {
min_flow = std::min(min_flow, ver[pre[v]].w);
}
for (int v = t; v != s; v = ver[pre[v] ^ 1].to) {
ver[pre[v]].w -= min_flow;
ver[pre[v] ^ 1].w += min_flow;
}
max_flow += min_flow;
}
return max_flow;
}
};
using EK = EK_<i64>;
Dinic
template <typename T> struct Flow_ {
const int n;
const T inf = numeric_limits<T>::max();
struct Edge {
int to;
T w;
Edge(int to, T w) : to(to), w(w) {}
};
vector<Edge> ver;
vector<vector<int>> h;
vector<int> cur, d;
Flow_(int n) : n(n + 1), h(n + 1) {}
void add(int u, int v, T c) {
h[u].push_back(ver.size());
ver.emplace_back(v, c);
h[v].push_back(ver.size());
ver.emplace_back(u, 0);
}
bool bfs(int s, int t) {
d.assign(n, -1);
d[s] = 0;
queue<int> q;
q.push(s);
while (!q.empty()) {
auto x = q.front();
q.pop();
for (auto it : h[x]) {
auto [y, w] = ver[it];
if (w && d[y] == -1) {
d[y] = d[x] + 1;
if (y == t) return true;
q.push(y);
}
}
}
return false;
}
T dfs(int u, int t, T f) {
if (u == t) return f;
auto r = f;
for (int &i = cur[u]; i < h[u].size(); i++) {
auto j = h[u][i];
auto &[v, c] = ver[j];
auto &[u, rc] = ver[j ^ 1];
if (c && d[v] == d[u] + 1) {
auto a = dfs(v, t, std::min(r, c));
c -= a;
rc += a;
r -= a;
if (!r) return f;
}
}
return f - r;
}
T work(int s, int t) {
T ans = 0;
while (bfs(s, t)) {
cur.assign(n, 0);
ans += dfs(s, t, inf);
}
return ans;
}
};
using Flow = Flow_<int>;
ISAP
template <typename T>
struct ISAP_ {
const int n;
const T inf = std::numeric_limits<T>::max();
struct Edge {
int to;
T w;
Edge(int to, T w) : to(to), w(w) {}
};
std::vector<Edge> ver;
std::vector<std::vector<int>> h;
std::vector<int> cur;
std::vector<int> d;
std::vector<int> pre;
std::vector<int> cnt;
ISAP_(int n) : n(n + 1), h(n + 1) {}
void add(int u, int v, T c) {
h[u].push_back((int)ver.size());
ver.emplace_back(v, c);
h[v].push_back((int)ver.size());
ver.emplace_back(u, 0);
}
bool bfs(int s, int t) {
d.assign(n, -1);
std::queue<int> q;
d[t] = 0;
q.push(t);
while (!q.empty()) {
int x = q.front();
q.pop();
for (int id : h[x]) {
int y = ver[id].to;
if (d[y] == -1) {
d[y] = d[x] + 1;
q.push(y);
}
}
}
for (int i = 1; i < n; ++i)
if (d[i] == -1) d[i] = n;
cnt.assign(n + 1, 0);
for (int i = 1; i < n; ++i)
if (d[i] <= n) ++cnt[d[i]];
return d[s] < n;
}
T dfs(int, int, T) { return 0; }
T work(int s, int t) {
if (!bfs(s, t)) return 0;
cur.assign(n, 0);
pre.assign(n, -1);
T max_flow = 0;
int x = s;
while (d[s] < n) {
bool advanced = false;
for (int &i = cur[x]; i < (int)h[x].size(); ++i) {
int id = h[x][i];
int v = ver[id].to;
if (ver[id].w > 0 && d[x] == d[v] + 1) {
pre[v] = id;
x = v;
advanced = true;
break;
}
}
if (!advanced) {
int min_d = n - 1;
for (int id : h[x]) {
if (ver[id].w > 0) {
min_d = std::min(min_d, d[ver[id].to]);
}
}
int old = d[x];
if (--cnt[old] == 0) break; // gap 优化
d[x] = min_d + 1;
++cnt[d[x]];
cur[x] = 0;
if (x != s) x = ver[pre[x] ^ 1].to; // 退回到前驱点
}
if (x == t) {
// 增广
T aug = inf;
for (int v = t; v != s; v = ver[pre[v] ^ 1].to) {
aug = std::min(aug, ver[pre[v]].w);
}
for (int v = t; v != s; v = ver[pre[v] ^ 1].to) {
int id = pre[v];
ver[id].w -= aug;
ver[id ^ 1].w += aug;
}
max_flow += aug;
x = s;
}
}
return max_flow;
}
};
using ISAP = ISAP_<i64>;
HLLP
template <typename T> struct PushRelabel {
const int inf = 0x3f3f3f3f;
const T INF = 0x3f3f3f3f3f3f3f3f;
struct Edge {
int to, cap, flow, anti;
Edge(int v = 0, int w = 0, int id = 0) : to(v), cap(w), flow(0), anti(id) {}
};
vector<vector<Edge>> e;
vector<vector<int>> gap;
vector<T> ex; // 超额流
vector<bool> ingap;
vector<int> h;
int n, gobalcnt, maxH = 0;
T maxflow = 0;
PushRelabel(int n) : n(n), e(n + 1), ex(n + 1), gap(n + 1) {}
void addedge(int u, int v, int w) {
e[u].push_back({v, w, (int)e[v].size()});
e[v].push_back({u, 0, (int)e[u].size() - 1});
}
void PushEdge(int u, Edge &edge) {
int v = edge.to, d = min(ex[u], 1LL * edge.cap - edge.flow);
ex[u] -= d;
ex[v] += d;
edge.flow += d;
e[v][edge.anti].flow -= d;
if (h[v] != inf && d > 0 && ex[v] == d && !ingap[v]) {
++gobalcnt;
gap[h[v]].push_back(v);
ingap[v] = 1;
}
}
void PushPoint(int u) {
for (auto k = e[u].begin(); k != e[u].end(); k++) {
if (h[k->to] + 1 == h[u] && k->cap > k->flow) {
PushEdge(u, *k);
if (!ex[u]) break;
}
}
if (!ex[u]) return;
if (gap[h[u]].empty()) {
for (int i = h[u] + 1; i <= min(maxH, n); i++) {
for (auto v : gap[i]) {
ingap[v] = 0;
}
gap[i].clear();
}
}
h[u] = inf;
for (auto [to, cap, flow, anti] : e[u]) {
if (cap > flow) {
h[u] = min(h[u], h[to] + 1);
}
}
if (h[u] >= n) return;
maxH = max(maxH, h[u]);
if (!ingap[u]) {
gap[h[u]].push_back(u);
ingap[u] = 1;
}
}
void init(int t, bool f = 1) {
ingap.assign(n + 1, 0);
for (int i = 1; i <= maxH; i++) {
gap[i].clear();
}
gobalcnt = 0, maxH = 0;
queue<int> q;
h.assign(n + 1, inf);
h[t] = 0, q.push(t);
while (q.size()) {
int u = q.front();
q.pop(), maxH = h[u];
for (auto &[v, cap, flow, anti] : e[u]) {
if (h[v] == inf && e[v][anti].cap > e[v][anti].flow) {
h[v] = h[u] + 1;
q.push(v);
if (f) {
gap[h[v]].push_back(v);
ingap[v] = 1;
}
}
}
}
}
T work(int s, int t) {
init(t, 0);
if (h[s] == inf) return maxflow;
h[s] = n;
ex[s] = INF;
ex[t] = -INF;
for (auto k = e[s].begin(); k != e[s].end(); k++) {
PushEdge(s, *k);
}
while (maxH > 0) {
if (gap[maxH].empty()) {
maxH--;
continue;
}
int u = gap[maxH].back();
gap[maxH].pop_back();
ingap[u] = 0;
PushPoint(u);
if (gobalcnt >= 10 * n) {
init(t);
}
}
ex[s] -= INF;
ex[t] += INF;
return maxflow = ex[t];
}
};


浙公网安备 33010602011771号