[关闭]
@ZCDHJ 2018-09-28T13:41:43.000000Z 字数 5246 阅读 677

网络流 学习笔记

网络流


网络流 学习笔记

一些概念

​ 举个栗子:现有 根水管连接 个点,每根水管每秒通水量的限制为 ,那么求从点 输水到 一秒最多能输多少水,假设水的速度是无限快的。这样子的问题就是最简单的最大流问题。

​ 我们设 两个点之间水管的容量为 实际流向 的水,那么 称为这条水管的剩余容量 。在任何时刻中,网络中剩余容量大于 的边构成的图叫做残量网络

会满足下面的限制

最大流

​ 上面例子里的 其实叫做源点, 叫做汇点。最大流问题就跟上面那个问题一样,求源点到汇点的最大流量。求解最大流问题有很多种算法,笔者比较菜,只会 EK 和 Dinic。

Edmonds-Karp 增广路算法

​ EK 算法,全称 Edmonds-Karp 增广路算法。其主要思路是使用 BFS 找到一条从 的路径,其路径上所有边的剩余容量都大于 。我们将这样的一条路径称之为增广路。很明显,这样子可能得不到最优解,所以要建反向边,相当于是可以撤销这条边的流量。

时间复杂度为

以下模板用来 AC Luogu 【模板】网络最大流

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <queue>
  4. #include <cstring>
  5. const int INF = 0x3f3f3f3f;
  6. const int MAXN = 1e4;
  7. const int MAXM = 1e5;
  8. int n, m, s, t, edge = -1;
  9. int firstEdge[MAXN | 1], minFlow[MAXN | 1], F[MAXN | 1];
  10. struct Edge {
  11. int to, w, nxt;
  12. Edge(){}
  13. Edge(int x, int y, int z) {
  14. to = y;
  15. w = z;
  16. nxt = firstEdge[x];
  17. }
  18. } e[MAXM << 1 | 1];
  19. inline int read() {
  20. register int x = 0;
  21. register char ch = getchar();
  22. while(!isdigit(ch)) ch = getchar();
  23. while(isdigit(ch)) {
  24. x = x * 10 + ch - '0';
  25. ch = getchar();
  26. }
  27. return x;
  28. }
  29. inline void addEdge(int x, int y, int z) {
  30. e[++edge] = Edge(x, y, z);
  31. firstEdge[x] = edge;
  32. }
  33. bool Augment() {
  34. bool vis[MAXN | 1];
  35. std::queue <int> q;
  36. memset(vis, 0, sizeof(vis));
  37. q.push(s);
  38. vis[s] = 1;
  39. minFlow[s] = INF;
  40. do {
  41. int from = q.front();
  42. q.pop();
  43. for(int k = firstEdge[from]; k != -1; k = e[k].nxt) {
  44. int to = e[k].to, w = e[k].w;
  45. if(w > 0) {
  46. if(vis[to] == true) continue;
  47. minFlow[to] = std::min(minFlow[from], w);
  48. F[to] = k;
  49. q.push(to);
  50. vis[to] = 1;
  51. if(to == t) return true;
  52. }
  53. }
  54. } while(!q.empty());
  55. return false;
  56. }
  57. int EK() {
  58. int res = 0;
  59. while(Augment() == true) {
  60. res += minFlow[t];
  61. int tmp = t;
  62. while(tmp != s) {
  63. int k = F[tmp];
  64. e[k].w -= minFlow[t];
  65. e[k ^ 1].w += minFlow[t];
  66. tmp = e[k ^ 1].to;
  67. }
  68. }
  69. return res;
  70. }
  71. int main() {
  72. n = read();
  73. m = read();
  74. s = read();
  75. t = read();
  76. memset(firstEdge, -1, sizeof(firstEdge));
  77. for(int i = 1; i <= m; ++i) {
  78. int u = read(), v = read(), w = read();
  79. addEdge(u, v, w);
  80. addEdge(v, u, 0);
  81. }
  82. printf("%d\n", EK());
  83. return 0;
  84. }

Dinic 算法

​ Dinic 算法主要思路也是寻找增广路。每次寻找增广路的时候,求出当前残量网络的 BFS 序,然后每次在残量网络上 DFS 找增广路从 点出发只能访问到 BFS 序为 的 BFS 序 的点。这样子的时间复杂度是 ,比较优秀。

以下模板用来 AC Luogu 【模板】网络最大流

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <queue>
  5. const int INF = 0x3f3f3f3f;
  6. const int MAXN = 1e4;
  7. const int MAXM = 1e5;
  8. int n, m, sp, ep, edge = -1;
  9. int firstEdge[MAXN | 1], curE[MAXN | 1], depth[MAXN | 1];
  10. struct Edge {
  11. int to, w, nxt;
  12. Edge(){}
  13. Edge(int x, int y, int z) {
  14. to = y;
  15. w = z;
  16. nxt = firstEdge[x];
  17. }
  18. } e[MAXM << 1 | 1];
  19. inline int read() {
  20. register int x = 0;
  21. register char ch = getchar();
  22. while(!isdigit(ch)) ch = getchar();
  23. while(isdigit(ch)) {
  24. x = x * 10 + ch - '0';
  25. ch = getchar();
  26. }
  27. return x;
  28. }
  29. inline void addEdge(int x, int y, int z) {
  30. e[++edge] = Edge(x, y, z);
  31. firstEdge[x] = edge;
  32. }
  33. bool getDepth() {
  34. std::queue <int> q;
  35. memset(depth, 0, sizeof(depth));
  36. depth[sp] = 1;
  37. q.push(sp);
  38. do {
  39. int from = q.front();
  40. q.pop();
  41. for(int k = firstEdge[from]; ~k; k = e[k].nxt) {
  42. int to = e[k].to, w = e[k].w;
  43. if(!depth[to] && w > 0) {
  44. depth[to] = depth[from] + 1;
  45. q.push(to);
  46. }
  47. }
  48. } while(!q.empty());
  49. return depth[ep] > 0;
  50. }
  51. int Augment(int x, int flow) {
  52. if(x == ep) return flow;
  53. for(int &k = curE[x]; ~k; k = e[k].nxt) {
  54. int to = e[k].to, w = e[k].w;
  55. if(depth[to] == depth[x] + 1 && w > 0) {
  56. int tmp = Augment(to, std::min(flow, w));
  57. if(tmp > 0) {
  58. e[k].w -= tmp;
  59. e[k ^ 1].w += tmp;
  60. return tmp;
  61. }
  62. }
  63. }
  64. return 0;
  65. }
  66. int Dinic() {
  67. int res = 0, tmp = 0;
  68. while(getDepth()) {
  69. for(int i = 1; i <= n; ++i) curE[i] = firstEdge[i];
  70. while((tmp = Augment(sp, INF)) > 0) res += tmp;
  71. }
  72. return res;
  73. }
  74. int main() {
  75. n = read();
  76. m = read();
  77. sp = read();
  78. ep = read();
  79. memset(firstEdge, -1, sizeof(firstEdge));
  80. for(int i = 1; i <= m; ++i) {
  81. int a = read(), b = read(), c = read();
  82. addEdge(a, b, c);
  83. addEdge(b, a, 0);
  84. }
  85. printf("%d\n", Dinic());
  86. return 0;
  87. }

最小割

​ 给定一个网络 ,若某个 的子边集被删去,使得源点与汇点不再连通,则该边集称为网络的一个。容量之和最小的割称为网络的最小割

最大流最小割定理

任何一个网络里的最大流与最小割的容量相等。

下面来理性感性理解一下:

​ 为了使得源点与汇点不连通,每条源点通往汇点的路径上至少要删去一条边,也就是进入割集。那么贪心的想一想,肯定要删去容量最小的那条边。因为每条路径的流量取决于路径上容量最小的那条边,所以最小割=最大流。

USACO4.4 Pollutant Control

费用流

​ 在之前问题的基础上给每条边除容量外还加上了一个花费,其总花费为总流量乘以总花费。其中总花费最小的最大流称为最小费用最大流,而花费最大的称为最大费用最大流。二者合称费用流模型费用流的前提是最大流

我只会 EK 魔改的 SPFA

这个算法的思想就是在 EK 的基础下,找增广路的时候用 SPFA 找一条总费用最小的增广路,剩下的部分与 EK 差不多。

复杂度为 ,其中 为最大流量。

以下代码用来 AC Luogu 【模板】最小费用最大流

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <queue>
  5. const int INF = 0x3f3f3f3f;
  6. const int MAXN = 5e4;
  7. const int MAXM = 5e5;
  8. int n, m, sp, ep, edge = -1, maxFlow, minCost;
  9. int firstEdge[MAXN | 1], minFlow[MAXN | 1], dist[MAXN | 1], lst[MAXN | 1];
  10. struct Edge {
  11. int to, f, w, nxt;
  12. Edge(){}
  13. Edge(int a, int b, int c, int d) {
  14. to = b;
  15. f = c;
  16. w = d;
  17. nxt = firstEdge[a];
  18. }
  19. } e[MAXM << 1 | 1];
  20. inline int read() {
  21. register int x = 0, v = 1;
  22. register char ch = getchar();
  23. while(!isdigit(ch)) {
  24. if(ch == '-') v = -1;
  25. ch = getchar();
  26. }
  27. while(isdigit(ch)) {
  28. x = x * 10 + ch - '0';
  29. ch = getchar();
  30. }
  31. return x * v;
  32. }
  33. inline void addEdge(int a, int b, int c, int d) {
  34. e[++edge] = Edge(a, b, c, d);
  35. firstEdge[a] = edge;
  36. }
  37. bool Augment() {
  38. std::queue <int> q;
  39. bool vis[MAXN | 1];
  40. while(!q.empty()) q.pop();
  41. memset(vis, false, sizeof(vis));
  42. memset(dist, INF, sizeof(dist));
  43. dist[sp] = 0;
  44. minFlow[sp] = INF;
  45. q.push(sp);
  46. do {
  47. int from = q.front();
  48. q.pop();
  49. vis[from] = false;
  50. for(int k = firstEdge[from]; ~k; k = e[k].nxt) {
  51. int to = e[k].to, w = e[k].w, f = e[k].f;
  52. if(f > 0 && dist[to] > dist[from] + w) {
  53. dist[to] = dist[from] + w;
  54. minFlow[to] = std::min(minFlow[from], f);
  55. lst[to] = k;
  56. if(!vis[to]) {
  57. q.push(to);
  58. vis[to] = true;
  59. }
  60. }
  61. }
  62. } while(!q.empty());
  63. return dist[ep] != INF;
  64. }
  65. void EK(int flow) {
  66. while(flow > 0 && Augment()) {
  67. maxFlow += minFlow[ep];
  68. minCost += minFlow[ep] * dist[ep];
  69. flow -= minFlow[ep];
  70. int tmp = ep;
  71. while(tmp != sp) {
  72. e[lst[tmp]].f -= minFlow[ep];
  73. e[lst[tmp] ^ 1].f += minFlow[ep];
  74. tmp = e[lst[tmp] ^ 1].to;
  75. }
  76. }
  77. }
  78. int main() {
  79. n = read();
  80. m = read();
  81. sp = read();
  82. ep = read();
  83. memset(firstEdge, -1, sizeof(firstEdge));
  84. for(int i = 1; i <= m; ++i) {
  85. int a = read(), b = read(), c = read(), d = read();
  86. addEdge(a, b, c, d);
  87. addEdge(b, a, 0, -d);
  88. }
  89. EK(INF);
  90. printf("%d %d\n", maxFlow, minCost);
  91. return 0;
  92. }

POJ 3422

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注