50.网络流
网络流
最大流问题
如图,我们需要把一些物品从节点
图 b 展示了一种可能的方案,每一条边的第一个数字表示实际运送的物品数目,称为流量记录
注意,把 5 个物品从
这样规定
我们很容易发现几个性质:
- 容量限制
- 斜对称性
- 流量平衡对于除了
和 之外的节点 ,
我们的目标是最大化
增广路算法
那么如何解决网络流问题呢?一种算法思想很简单,从 0 流开始不断增加流量。
外面计算每一条边上容量与流量之差(称为残余容量,简称残量),得到了一个残量网络。例如
残量网络中任何一条从
Edmonds-Karp 算法
"找任意路径"最简单的方法是用 DFS,但是可以造数据把 DFS 卡死,所以我们使用 BFS 来找到一条增广路,这就是 Edmonds-Karp 算法。
相当于,我要找到一条从起点到终点的通路,且这条路上都要满足
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f;
struct EdmondsKarp {
int n, m; // n 为点数,m 为边数
struct Edge {
int from, to, cap, flow; // 起点,终点,容量,流量
};
vector<Edge> edges; // 边表。edges[e] 和 edges[e^1] 互为反向边
vector<vector<int>> g; // 邻接表,G[i][j] 表示节点 i 的第 j 条边在 edges 中的序号
void init (int n_) {
n = n_; edges.clear();
g.assign(n, vector<int>());
}
void add_e (int from, int to, int cap) {
edges.push_back(Edge{from, to, cap, 0});
edges.push_back(Edge{to, from, 0, 0});
m = edges.size();
g[from].push_back(m - 2);
g[to].push_back(m - 1);
}
int max_flow (int s, int t) {
int flow = 0;
vector<int> a, pre (n, 0); // a 为当前节点到源点的可改进量, pre 为当前节点的前驱的边的编号
while (true) {
a.assign(n, 0);
queue<int> q; q.push(s); a[s] = INF;
while (!q.empty()) {
int x = q.front(); q.pop();
for (auto i : g[x]) {
Edge &e = edges[i];
if (a[e.to] == 0 && e.cap > e.flow) { // 未访问过且残留网络中有剩余容量
pre[e.to] = i;
a[e.to] = min(a[x], e.cap - e.flow); // 更新到源点的可改进量
q.push(e.to);
}
}
if (a[t]) break; // 已经找到增广路
}
if (a[t] == 0) break; // 没有增广路
for (int u = t; u != s; u = edges[pre[u]].from) { // 从汇点向源点更新流量
edges[pre[u]].flow += a[t];
edges[pre[u] ^ 1].flow -= a[t];
}
flow += a[t];
}
return flow;
}
};
struct Dinic {
struct Edge {
int from, to, cap, flow;
};
int n, m, s, t;
vector<Edge> edges;
vector<vector<int>> g;
vector<int> d, cur; // d 为层次,cur 为当前弧优化
void init (int n_) {
n = n_; edges.clear();
g.assign(n, vector<int>());
}
};
signed main() {
freopen ("P3376.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, s, t; cin >> n >> m >> s >> t;
EdmondsKarp ek; ek.init(n + 1);
for (int i = 0; i < m; i++) {
int u, v, c; cin >> u >> v >> c;
ek.add_e(u, v, c);
}
cout << ek.max_flow(s, t) << '\n';
return 0;
}
以上代码中,每条弧和他的反向弧保存在一起,且一个是奇数,一个是偶数,所以边
在 BFS 的时候,时刻记下从
Dinic 算法
可以证明每次沿着最短增广路进行增广,这样最多需要
Edmonds-Karp 算法使用 BFS 来进行查找,需要
Dinic 算法,宏观上讲,就是不停的用 BFS 构造层次图,然后用阻塞流来增广。
什么是层次图?在残量网络中,起点到结点
阻塞流是什么?实际上就是不考虑反向弧的极大流。
对应到程序里面就是从起点开始,在层次图中 DFS,每找到一条路就增广。
下面是 3 次增广的过程
这 3 次增广后,层次图中不存在
尽管在上文中我们仅在单条增广路上定义了增广/增广流,广义地,「增广」一词不仅可以用于单条路径上的增广流,也可以用于若干增广流的并——后者才是我们定义阻塞流时使用的意义。
实现过程如下:
- 在原图上 BFS 出分层图
- 在分层图中 DFS 出阻塞流
- 将
并到原先的流 中 - 重复以上过程直到不存在从
到 的路径
此时的
当前弧优化
注意到,在分层图上 DFS 的过程中,如果节点
复杂度
可以证明,Dinic 的时间复杂度为
struct Dinic {
struct Edge {
int from, to, cap, flow;
};
int n, m, s, t;
vector<Edge> edges;
vector<vector<int>> g;
vector<int> d, cur; // d 为层次,cur 为当前弧优化
void init (int n_) {
n = n_; edges.clear();
d.assign(n, 0);
g.assign(n, vector<int>());
}
void add_e (int from, int to, int cap) {
edges.push_back(Edge{from, to, cap, 0});
edges.push_back(Edge{to, from, 0, 0});
m = edges.size();
g[from].push_back(m - 2);
g[to].push_back(m - 1);
}
bool bfs () {
vector<int> vis (n, 0);
queue<int> q; q.push(s); d[s] = 0; vis[s] = 1;
while (!q.empty()) {
int x = q.front(); q.pop();
for (auto i : g[x]) {
Edge &e = edges[i];
if (vis[e.to] == 0 && e.cap > e.flow) {
vis[e.to] = 1;
d[e.to] = d[x] + 1;
q.push(e.to);
}
}
}
return vis[t]; // 是否存在能到达汇点的路径
}
int dfs (int x, int a) { // a 表示从源点到 x 的可改进量
if (x == t || a == 0) return a;
int flow = 0, f;
for (int &i = cur[x]; i < g[x].size(); i++) { // 当前弧优化,在 cur[x] 之前都没有增广成功
Edge &e = edges[g[x][i]];
if (d[x] + 1 == d[e.to] && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0) {
e.flow += f;
edges[g[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
int max_flow (int s, int t) {
this->s = s; this->t = t;
int flow = 0;
while (bfs()) {
cur.assign(n, 0);
flow += dfs(s, INF);
}
return flow;
}
};
signed main() {
freopen ("P3376.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, s, t; cin >> n >> m >> s >> t;
Dinic ek; ek.init(n + 1);
for (int i = 0; i < m; i++) {
int u, v, c; cin >> u >> v >> c;
ek.add_e(u, v, c);
}
cout << ek.max_flow(s, t) << '\n';
return 0;
}
牛客的一道题,这样就能求出最小割的边集了
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
struct Dinic {
struct Edge {
int from, to, cap, flow;
};
int n, m, s, t;
vector<Edge> edges;
vector<vector<int>> g;
vector<int> d, cur;
void init(int n_) {
n = n_; edges.clear();
d.assign(n, 0);
g.assign(n, vector<int>());
}
void add_e(int from, int to, int cap) {
edges.push_back({from, to, cap, 0});
edges.push_back({to, from, 0, 0});
m = edges.size();
g[from].push_back(m - 2);
g[to].push_back(m - 1);
}
bool bfs() {
fill(d.begin(), d.end(), -1);
queue<int> q; q.push(s); d[s] = 0;
while (!q.empty()) {
int x = q.front(); q.pop();
for (auto i : g[x]) {
Edge &e = edges[i];
if (d[e.to] == -1 && e.cap > e.flow) {
d[e.to] = d[x] + 1;
q.push(e.to);
}
}
}
return d[t] != -1;
}
int dfs(int x, int a) {
if (x == t || a == 0) return a;
int flow = 0, f;
for (int &i = cur[x]; i < g[x].size(); i++) {
Edge &e = edges[g[x][i]];
if (d[x] + 1 == d[e.to] && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0) {
e.flow += f;
edges[g[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
if (flow == 0) d[x] = -1; // 表示从 x 出发找不到增广路
return flow;
}
int max_flow(int s, int t) {
this->s = s; this->t = t;
int flow = 0;
while (bfs()) {
cur.assign(n, 0);
flow += dfs(s, INF);
}
return flow;
}
};
int main() {
int n, m, k; cin >> n >> m >> k;
Dinic dinic; dinic.init(2 * n * k + 2); int S = 0, T = 2 * n * k + 1;
auto get_id = [&] (int x, int j, int op) {
return (x - 1) * k * 2 + j * 2 + op + 1;
};
auto inv_id = [&] (int id) {
id--; int x = (id - 1) / 2 / k + 1, j = (id - 1) / 2 % k, op = (id - 1) % 2;
return x;
};
vector<vector<int>> g(n + 1);
vector<int> du_in(n + 1), du_out(n + 1);
for (int i = 1; i <= m; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v);
du_out[u]++; du_in[v]++;
}
for (int i = 1; i <= n; i++) {
if (du_in[i] == 0 || du_out[i] == 0) {
if (du_in[i] == 0) dinic.add_e(S, get_id(i, 0, 1), INF);
if (du_out[i] == 0) dinic.add_e(get_id(i, k - 1, 0), T, INF);
}
else {
for (int j = 0; j < k; j++) {
dinic.add_e(get_id(i, j, 0), get_id(i, j, 1), 1);
if (j + 1 < k) dinic.add_e(get_id(i, j, 0), get_id(i, j + 1, 1), INF);
}
}
int u = i;
for (auto v : g[u]) {
for (int j = 0; j < k; j++)
dinic.add_e(get_id(u, j, 1), get_id(v, j, 0), INF);
}
}
auto siz = dinic.max_flow(S, T);
if (siz > n) {
cout << -1 << '\n';
return 0;
}
cout << siz << '\n';
vector<int> ans;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < k; j++) {
if(dinic.d[get_id(i, j, 0)] != -1 && dinic.d[get_id(i, j, 1)] == -1) {
// 说明断了 i 这个点
ans.push_back(i);
}
}
}
for (auto x : ans) cout << x << ' ';
return 0;
}
最小割
有一个与最大流关系密切的问题,最小割。
把所有的顶点分成两个集合
如果把起点在
它的容量定义为
最小割就是求得一个割
最小割最大流定理
可以从另外一个角度来看待割,从
注意这里的割是任取的,因此我们得到了一个重要结论,对于任意
在残量网络中若不存在增广路,那么说明残量网络中
费用流
假设每条边除了有一个容量限制外,还有一个单位流量所需的费用 (cost)。
图中用
SSP 算法
SSP(Successive Shortest Path)算法是一个贪心的算法。它的思路是 每次寻找单位费用最小的增广路进行增广,直到图上不存在增广路为止。
什么时候单位费用最小?就是把费用看成距离,单位费用最小看成最短路
如果图上存在单位费用为负的圈,SSP 算法无法正确求出该网络的最小费用最大流。此时需要先使用消圈算法消去图上的负圈。
最小费用路算法,和 Edmonds-Karp ,Dinic 算法类似,但是每次用 Bellman-Ford 算法增广路,因为 Bellman-Ford 可以消去负权回路
贪心的思路:只要初始流是该流量下的最小费用可行流,每次增广后的新流都是新流量下的最小费用流。
基于 Edmonds-Karp 的实现
const int INF = 0x3f3f3f3f;
struct EdmondsKarp {
struct Edge {
int from, to, cap, flow, cost;
};
int n, m;
vector<Edge> edges;
vector<vector<int>> g;
void init(int n_) {
n = n_; edges.clear();
g.assign(n, vector<int>());
}
void add_e (int from, int to, int cap, int cost) {
edges.push_back(Edge{from, to, cap, 0, cost});
edges.push_back(Edge{to, from, 0, 0, -cost});
m = edges.size();
g[from].push_back(m - 2);
g[to].push_back(m - 1);
}
bool bellmon_ford (int s, int t, int &flow, int &cost) {
vector<int> dis (n, INF), a(n, 0), pre(n, 0), vis(n, 0);
// dis 为源点到当前点的最短距离, a 为当前节点到源点的可改进量, pre 为当前节点的前驱的边的编号, vis 为当前节点是否在队列中
queue<int> q; q.push(s); dis[s] = 0; a[s] = INF;
while (!q.empty()) {
int x = q.front(); q.pop();
vis[x] = 0;
for (auto i : g[x]) {
Edge &e = edges[i];
if (e.cap > e.flow && dis[e.to] > dis[x] + e.cost) {
dis[e.to] = dis[x] + e.cost;
pre[e.to] = i;
a[e.to] = min(a[x], e.cap - e.flow);
if (!vis[e.to]) {q.push(e.to); vis[e.to] = 1;}
}
}
}
if (dis[t] == INF) return false;
flow += a[t]; cost += dis[t] * a[t]; // 更新流量和费用
for (int u = t; u != s; u = edges[pre[u]].from) {
edges[pre[u]].flow += a[t];
edges[pre[u] ^ 1].flow -= a[t];
}
return true;
}
pair<int, int> min_cost_max_flow(int s, int t) { // 返回最大流和最小费用
int flow = 0, cost = 0;
while (bellmon_ford(s, t, flow, cost));
return {flow, cost};
}
};
int main() {
freopen ("P3381.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m, s, t; cin >> n >> m >> s >> t;
EdmondsKarp ek; ek.init(n + 1);
for (int i = 0; i < m; i++) {
int u, v, c, w; cin >> u >> v >> c >> w;
ek.add_e(u, v, c, w);
}
auto [flow, cost] = ek.min_cost_max_flow(s, t);
cout << flow << ' ' << cost << '\n';
return 0;
}
基于 Dinic 的代码实现
struct Dinic {
int n, m, s, t;
struct Edge {
int from, to, cap, flow, cost;
};
vector<Edge> edges;
vector<vector<int>> g;
vector<int> d, cur, vis;
void init(int n_) {
n = n_; edges.clear();
g.assign(n, vector<int>());
d.resize(n); cur.resize(n); vis.resize(n);
}
void add_e(int from, int to, int cap, int cost) {
edges.push_back(Edge{from, to, cap, 0, cost});
edges.push_back(Edge{to, from, 0, 0, -cost});
m = edges.size();
g[from].push_back(m - 2);
g[to].push_back(m - 1);
}
bool bellman_ford () {
fill (d.begin(), d.end(), INF);
fill (vis.begin(), vis.end(), 0);
queue<int> q; q.push(s); d[s] = 0; vis[s] = 1;
while (!q.empty()) {
int x = q.front(); q.pop();
vis[x] = 0;
for (auto i : g[x]) {
Edge &e = edges[i];
if (e.cap > e.flow && d[e.to] > d[x] + e.cost) {
d[e.to] = d[x] + e.cost;
if (!vis[e.to]) {q.push(e.to); vis[e.to] = 1;}
}
}
}
return d[t] != INF;
}
int dfs (int x, int a) {
if (x == t || a == 0) return a;
int flow = 0, f;
vis[x] = 1;
for (int &i = cur[x]; i < g[x].size(); i++) {
Edge &e = edges[g[x][i]];
if (!vis[e.to] && d[x] + e.cost == d[e.to] && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0) {
e.flow += f; edges[g[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
pair<int, int> min_cost_max_flow(int s, int t) {
this->s = s; this->t = t;
int flow = 0, cost = 0;
while (bellman_ford()) {
fill(cur.begin(), cur.end(), 0);
fill(vis.begin(), vis.end(), 0);
while (int f = dfs(s, INF)) {
flow += f;
cost += f * d[t];
fill(vis.begin(), vis.end(), 0);
}
}
return {flow, cost};
}
};
int main() {
freopen ("P3381.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m, s, t; cin >> n >> m >> s >> t;
Dinic ek; ek.init(n + 1);
for (int i = 0; i < m; i++) {
int u, v, c, w; cin >> u >> v >> c >> w;
ek.add_e(u, v, c, w);
}
auto [flow, cost] = ek.min_cost_max_flow(s, t);
cout << flow << ' ' << cost << '\n';
return 0;
}