经过了几天的学习和做题,我利用刘汝佳书上的网络流算法模板完成了HihoCoder上的几个网络流算法,HihoCoder可能还会继续更新网络流算法,所以我也会接着总结。
这个主要是对网络流算法的建模做分析和理解,不具体分析网络流算法,网络流算法会单独总结。
网络流一·Ford-Fulkerson算法
题目连接
本题没有建模,就是标准的网络最大流求解,将图建完后直接应用最大流算法即可解决。但在此记录几点注意的地方:
代码如下,为刘汝佳书《算法竞赛入门经典》中一个模板:
const int maxn = 505;const int INF = 0x7fffffff;struct Edge { int from, to, cap, flow; Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}};struct EdmondsKarp { int n, m; vector<Edge> edges; vector<int> G[maxn]; int a[maxn]; int p[maxn]; void init(int n) { for (int i = 0; i < n; ++i) G[i].clear(); edges.clear(); } void AddEdge(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 Maxflow(int s, int t) { int flow = 0; for (;;) { memset(a, 0, sizeof(a)); queue<int> Q; Q.push(s); a[s] = INF; while (!Q.empty()) { int x = Q.front(); Q.pop(); for (int i = 0; i < G[x].size(); ++i) { Edge &e = edges[G[x][i]]; if (!a[e.to] && e.cap > e.flow) { p[e.to] = G[x][i]; a[e.to] = min(a[x], e.cap - e.flow); Q.push(e.to); } } if (a[t]) break; } if (!a[t]) break; for (int u = t; u != s; u = edges[p[u]].from) { edges[p[u]].flow += a[t]; edges[p[u] ^ 1].flow -= a[t]; } flow += a[t]; } return flow; }};int main(){#ifdef LOCAL freopen("input.txt", "r", stdin); #endif int N, M, u, v, c; cin >> N >> M; EdmondsKarp ek; ek.init(N); for (int i = 0; i < M; ++i) { cin >> u >> v >> c; ek.AddEdge(u, v, c); } cout << ek.Maxflow(1, N) << endl; return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
网络流二·最大流最小割定理
题目连接
这部分主要是证明最小割等于最大流,证明详细步骤见上面题目,这里记录下主要步骤:
f(S, T) 等于从 s 出来的流,等于当前的网络流量 f。f(S, T) 表示割 (S, T)的净流量。
对于网络的任何一个流,一定小于等于任何一个割的容量(f(S, T) <= C(S, T)
对于一个网络 G=(V, E),有源点 s 汇点 t,以下三个等价:
1、f 是图 G 的最大流
2、残留网络不存在增广路
3、对于G的一个割(S, T),此时 f = C(S, T)
证明:
1=>2:假设 f 是图 G 的最大流,如果残留网络存在增广路 p,流量为 fp,那么有流 f’ = f + fp > f ,与 f 是最大流矛盾。
2=>3:对于任意的 u S v T,有 f(u, v) = c(u, v),即
∑f(u,v)=∑c(u,v)=f(S,T)=C(S,T)=f
这样,找不到增广路的时候求得的一定是最大流,最大流等于最小割。
另外,本题要求求出最小割集合 S,在割 (S, T) 中,计算出的残留网络从 s 开始遍历,所能遍历到的点即为 S 集合,因为求得的最小割就是最大流,最大流中残留网络不存在增广路径,也就是说从 s 没法走到 t,故从 s 开始遍历,所得到的点的集合就是 S。
const int maxn = 505;const int INF = 0x7FFFFFFF;struct Edge { int from, to, cap, flow; Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}};bool used[maxn];std::vector<int> rst;struct EdmondsKarp { int n, m; vector<Edge> edges; vector<int> G[maxn]; int a[maxn]; int p[maxn]; void init(int n) { for (int i = 0; i < n; ++i) G[i].clear(); edges.clear(); } void AddEdge(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 Maxflow(int s, int t) { int flow = 0; for (;;) { memset(a, 0, sizeof(a)); queue<int> Q; Q.push(s); a[s] = INF; while (!Q.empty()) { int x = Q.front(); Q.pop(); for (int i = 0; i < G[x].size(); ++i) { Edge &e = edges[G[x][i]]; if (!a[e.to] && e.cap > e.flow) { p[e.to] = G[x][i]; a[e.to] = min(a[x], e.cap - e.flow); Q.push(e.to); } } if (a[t]) break; } if (!a[t]) break; for (int u = t; u != s; u = edges[p[u]].from) { edges[p[u]].flow += a[t]; edges[p[u] ^ 1].flow -= a[t]; } flow += a[t]; } return flow; } void GetMinCutSetS(int s) { rst.push_back(s); used[s] = true; for (int i = 0; i < G[s].size(); ++i) { Edge &e = edges[G[s][i]]; if (!used[e.to] && e.flow != e.cap) { GetMinCutSetS(e.to); } } }};int main(int argc, char** argv) {#ifdef LOCAL freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout);#endif int N, M; cin >> N >> M; EdmondsKarp ek; ek.init(N); int u, v, c; for (int i = 0; i < M; ++i) { cin >> u >> v >> c; ek.AddEdge(u, v, c); } int flow = ek.Maxflow(1, N); ek.GetMinCutSetS(1); std::cout << flow << " " << rst.size() << std::endl; for (int i = 0; i < rst.size() - 1; ++i) { cout << rst[i] << " "; } std::cout << rst[rst.size() - 1] << std::endl; return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
说明:代码中的 GetMinCutSetS
就是一个 DFS 方法,从一个点开始遍历得到最终的 S 集合,没什么难的。结果保存在一个 vector 中。
网络流三·二分图多重匹配
题目连接
二分图的多重匹配,其实质就是需要规定 X 集中的点可以使用多少次,Y 中的点可以重用多少次,如果 X 中的某个点的流量使用完毕,则这条边满流,则不可再次使用。从源点 s 指向 X 集中的边的容量则规定了这个点能用多少次!Y 集中的指向汇点 t 的边的容量也是如此含义。所以,如果 Y 集中的容量没有用光,则说明当前的流(匹配)还没有达到所期望的要求。
这题使用了CheckMaxMatch
用来判断指向汇点的边是否满流。
const int maxn = 1005;const int INF = 0x7FFFFFFF;struct Edge { int from, to, cap, flow; Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}};struct EdmondsKarp { int n, m; vector<Edge> edges; vector<int> G[maxn]; int a[maxn]; int p[maxn]; void init(int n) { for (int i = 0; i < n; ++i) G[i].clear(); edges.clear(); } void AddEdge(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 Maxflow(int s, int t) { int flow = 0; for (;;) { memset(a, 0, sizeof(a)); queue<int> Q; Q.push(s); a[s] = INF; while (!Q.empty()) { int x = Q.front(); Q.pop(); for (int i = 0; i < G[x].size(); ++i) { Edge &e = edges[G[x][i]]; if (!a[e.to] && e.cap > e.flow) { p[e.to] = G[x][i]; a[e.to] = min(a[x], e.cap - e.flow); Q.push(e.to); } } if (a[t]) break; } if (!a[t]) break; for (int u = t; u != s; u = edges[p[u]].from) { edges[p[u]].flow += a[t]; edges[p[u] ^ 1].flow -= a[t]; } flow += a[t]; } return flow; } bool CheckMaxMatch(int N, int M) { for (int i = 1; i <= M; ++i) { for (int j = 0; j < G[N + i].size(); ++j) { Edge &e = edges[G[N + i][j]]; if (e.flow != e.cap && e.flow > 0) { return false; } } } return true; } };int main(int argc, char** argv) {#ifdef LOCAL freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout);#endif int T; cin >> T; while (T--) { int N, M; cin >> N >> M; EdmondsKarp ek; ek.init(N + M + 2); int m[maxn + 5], a[maxn + 5], b[maxn + 5]; for (int i = 0; i < M; ++i) cin >> m[i]; for (int i = 0; i < N; ++i) { cin >> a[i] >> b[i]; int tmprecv; for (int j = 0; j < b[i]; ++j) { cin >> tmprecv; ek.AddEdge(i + 1, tmprecv + N, 1); } } for (int i = 1; i <= N; ++i) { ek.AddEdge(0, i, a[i - 1]); } for (int i = 1; i <= M; ++i) { ek.AddEdge(N + i, N + M + 1, m[i - 1]); } ek.Maxflow(0, N + M + 1); cout << (ek.CheckMaxMatch(N, M) ? "Yes" : "No") << endl; } return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
网络流四·最小路径覆盖
题目连接
建图的方法为:
1、添加源点 s 和汇点 t。
2、拆点,将每个点拆成两个点,比如 a 拆成 a1, a2,b 拆成 b1, b2。
3、从源点向 X 集合中每个点添加一条容量为 1 的有向边。
4、从 Y 集合向汇点中每个点添加一条容量为 1 的有向边。
5、如果 a -> b 有边,则从 a1 向 b2 添加一条容量为 1 的有向边。
最小路径覆盖就是总点数 N - 最小割。证明在我学会之前暂时不写。
推荐去看《计算机算法设计与分析》中的网络流 24 题中的魔术球问题,这是一道很隐晦的利用网络流的最小路径覆盖问题,很经典。
const int maxn = 1005;const int INF = 0x7FFFFFFF;struct Edge { int from, to, cap, flow; Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}};struct EdmondsKarp { int n, m; vector<Edge> edges; vector<int> G[maxn]; int a[maxn]; int p[maxn]; void init(int n) { for (int i = 0; i < n; ++i) G[i].clear(); edges.clear(); } void AddEdge(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 Maxflow(int s, int t) { int flow = 0; for (;;) { memset(a, 0, sizeof(a)); queue<int> Q; Q.push(s); a[s] = INF; while (!Q.empty()) { int x = Q.front(); Q.pop(); for (int i = 0; i < G[x].size(); ++i) { Edge &e = edges[G[x][i]]; if (!a[e.to] && e.cap > e.flow) { p[e.to] = G[x][i]; a[e.to] = min(a[x], e.cap - e.flow); Q.push(e.to); } } if (a[t]) break; } if (!a[t]) break; for (int u = t; u != s; u = edges[p[u]].from) { edges[p[u]].flow += a[t]; edges[p[u] ^ 1].flow -= a[t]; } flow += a[t]; } return flow; }};int main(int argc, char** argv) {#ifdef LOCAL freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout);#endif int N, M; cin >> N >> M; EdmondsKarp edk; edk.init(N + N); int u, v; for (int i = 1; i <= M; ++i) { cin >> u >> v; edk.AddEdge(u, v + N, 1); } for (int i = 1; i <= N; ++i) { edk.AddEdge(0, i, 1); edk.AddEdge(N + i, N + N + 1, 1); } cout << N - edk.Maxflow(0, N + N + 1) << endl; return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
网络流五·最大权闭合子图
题目连接
最大权闭合子图:目前就我的理解是用来建模求解一些有“收入”以及“支出”并且求最后最大的收益类问题的。建模方法如下:
1、添加源点 s 和汇点 t 。
2、从源点 s 向 X 集合中每个点连一条容量为该点“收入”的有向边。
3、从 Y 集合中每个点向汇点 t 连一条容量为该点“支出”的有向边。
4、若 X 和 Y 集合中的点有依赖关系,则从 X 集合向 Y 集合每个关系添加一条容量为无限大的有向边。
最终的结果为所有收入之和 - 最小割。
const int maxn = 1005;const int INF = 0x7FFFFFFF;struct Edge { int from, to, cap, flow; Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}};struct EdmondsKarp { int n, m; vector<Edge> edges; vector<int> G[maxn]; int a[maxn]; int p[maxn]; void init(int n) { for (int i = 0; i < n; ++i) G[i].clear(); edges.clear(); } void AddEdge(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 Maxflow(int s, int t) { int flow = 0; for (;;) { memset(a, 0, sizeof(a)); queue<int> Q; Q.push(s); a[s] = INF; while (!Q.empty()) { int x = Q.front(); Q.pop(); for (int i = 0; i < G[x].size(); ++i) { Edge &e = edges[G[x][i]]; if (!a[e.to] && e.cap > e.flow) { p[e.to] = G[x][i]; a[e.to] = min(a[x], e.cap - e.flow); Q.push(e.to); } } if (a[t]) break; } if (!a[t]) break; for (int u = t; u != s; u = edges[p[u]].from) { edges[p[u]].flow += a[t]; edges[p[u] ^ 1].flow -= a[t]; } flow += a[t]; } return flow; } bool CheckMaxMatch(int N, int M) { for (int i = 1; i <= M; ++i) { for (int j = 0; j < G[N + i].size(); ++j) { Edge &e = edges[G[N + i][j]]; if (e.flow != e.cap && e.flow > 0) { return false; } } } return true; } };int main(int argc, char** argv) {#ifdef LOCAL freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout);#endif int N, M; cin >> N >> M; int b[maxn], sum = 0; EdmondsKarp ek; ek.init(N + M + 2); for (int i = 1; i <= M; ++i) cin >> b[i]; for (int i = 1; i <= N; ++i) { int a, k, recvtmp; cin >> a >> k; sum += a; ek.AddEdge(0, i, a); for (int j = 1; j <= k; ++j) { cin >> recvtmp; ek.AddEdge(i, recvtmp + N, INF); } } for (int i = 1; i <= M; ++i) { ek.AddEdge(i + N, N + M + 1, b[i]); } cout << sum - ek.Maxflow(0, N + M + 1) << endl; return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109