网络流初步总结

本文介绍了最大流相关概念,包括源点、汇点、容量、流量等,阐述了最大流问题中容量和流量需满足的三个性质。还讲解了增广路算法,基于残量网络找增广路来增加流量,指出残量网中无增广路时当前流为最大流。最后给出hdu 3549最大流增广路入门模板题及最大流模板。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载于:https://blog.youkuaiyun.com/freezhanacmore/article/details/9366053

查看资料:lrj 《算法竞赛入门经典》

相关概念:

最大流:(Maximum-Flow Problem)

      从源点 S  中间经过一些点,一些的物品运送到汇点 t 。

              中途每两点间都有个最大运送物品数。

              求从 s 到 t 最多能运送多少物品。

                                           


容量: 对于一条边 (u,v),它的物品上限(能够运送的物品最大数量)称为容量 (capacity),

    记为 c(u,v) (对于不存在的边 (u,v) , c(u,v) = 0)

流量: 实际运送物品数称为流量 (flow)

            规定:f(u,v) 和 f(v,u) 最多只有一个正数(可以均为 0),且 f(u,v) = - f(v,u)

                                        

PS:此图左边表示实际运送物品,右边表示最大容量。


结论:对于除了 s 和 t 的任意节点 u,  ∑ f(u,v)  = 0 (有些 f 为负数) 。

                                                           (u,v)∈E


最大流问题中: 容量 c 和 流量 f 满足三个性质

  容量限制 f(u,v) <= c(u,v)

  斜对称:f(u, v) = -f(u,v)

  流量平衡:对于除了 s 和 t 的任意节点 u,  ∑ f(u,v)  = 0 (有些 f 为负数) 。

                                                                                            (u,v)∈E

目标:最大化 | f |  = ∑ f(s,v)       =    ∑ f(u,t)             即从 S 点流出的净流量(=流入 t 点的净流量) 

                                 (s,v)∈E ,         (u,t)∈E


增广路算法:

残量:上图中每条边上的容量差 (称为残余流量,简称残量),

           比如说上面第二个图中 V2 到 V4 残量为 14-11 = 3; V4 到 V2 残量为 0-(-11)= 11

算法基于事实:

           残量网络中任何一个从 s 到 t 的有向道路都对应一条原图中的增广路【PS:不理解这个名词也没事继续看】。

           只要求出该道路中所有残量的最小值 d,把对应的所有边上的流量增加 d 即可,这个过程称为增广。

  也就是说只要有从起点 s 到终点 t 的路上存在流量,那么找出最小的残余流量 d

           那么这个 d 肯定是满足这条路径的每一条边的,否则找不出这样的 d

           那么这条路径上的每一条边的流量增加 d ,总流量增加 d 就好了。

           然后继续找,直到找不到为止。

  

不难证明如果增广前的流量满足 3 个条件,那么增广之后任然满足。

显然只要残量网中存在增广路,流量就可以增大。

逆命题:如果残量网中不存在增广路,则当前流就是最大流,这就是著名的增广路定理。


问题:如何找路径? DFS ms 很慢,用 BFS


   
  1. queue< int> q;
  2. memset(flow, 0, sizeof(flow)); //初始化流量为 0
  3. f = 0; // 初始化总流量为 0
  4. for(;;) //BFS 找增广路
  5. {
  6. memset(a, 0, sizeof(a)); // a[i]:从起点 s 到 i 的最小残量【每次for()时 a[] 重新清 0 因此同时可做标记数组 vis】
  7. a[s] = INF; //起点残量无线大
  8. q.push(s); //起点入队
  9. while(!q.empty()) // BFS 找增广路
  10. {
  11. int u = q.front(); //取队首
  12. q.pop(); // 出队
  13. for( int v = 1; v <= n; v++) if(!a[v] && cap[u][v] > flow[u][v]) //找新节点 v
  14. {
  15. p[v] = u; q.push(v); //记录 v 的父亲节点,并加入 FIFO 队列
  16. a[v] = min(a[u], cap[u][v]-flow[u][v]); // s-v 路径上的最小残量【从而保证了最后,每条路都满足a[t]】
  17. }
  18. }
  19. if(a[t] == 0) break; // 找不到,则当前流已经是最大流 【t为终点】
  20. for( int u = t; u != s; u = p[u]) // 从汇点往回走
  21. {
  22. flow[p[u]][u] += a[t]; // 更新正向流
  23. flow[u][p[u]] -= a[t]; // 更新反向流
  24. }
  25. f += a[t]; // 更新从 S 流出的总流量
  26. }

推荐入门题目: 

hdu 3549 Flow Problem【最大流增广路入门模板题】


另外下面copy了下KB神给的最大流模板

暂时没有看懂Orz

最大流模板


   
  1. const int MAXN= 20010; //点数的最大值
  2. const int MAXM= 880010; //边数的最大值
  3. const int INF= 0x3f3f3f3f;
  4. struct Node
  5. {
  6. int from,to,next;
  7. int cap;
  8. }edge[MAXM];
  9. int tol;
  10. int head[MAXN];
  11. int dep[MAXN];
  12. int gap[MAXN]; //gap[x]=y :说明残留网络中dep[i]==x的个数为y
  13. int n; //n是总的点的个数,包括源点和汇点
  14. void init()
  15. {
  16. tol= 0;
  17. memset(head, -1, sizeof(head));
  18. }
  19. void addedge(int u,int v,int w)
  20. {
  21. edge[tol].from=u;
  22. edge[tol].to=v;
  23. edge[tol].cap=w;
  24. edge[tol].next=head[u];
  25. head[u]=tol++;
  26. edge[tol].from=v;
  27. edge[tol].to=u;
  28. edge[tol].cap= 0;
  29. edge[tol].next=head[v];
  30. head[v]=tol++;
  31. }
  32. void BFS(int start,int end)
  33. {
  34. memset(dep, -1, sizeof(dep));
  35. memset(gap, 0, sizeof(gap));
  36. gap[ 0]= 1;
  37. int que[MAXN];
  38. int front,rear;
  39. front=rear= 0;
  40. dep[end]= 0;
  41. que[rear++]=end;
  42. while(front!=rear)
  43. {
  44. int u=que[front++];
  45. if(front==MAXN)front= 0;
  46. for( int i=head[u];i!= -1;i=edge[i].next)
  47. {
  48. int v=edge[i].to;
  49. if(edge[i].cap!= 0||dep[v]!= -1) continue;
  50. que[rear++]=v;
  51. if(rear==MAXN)rear= 0;
  52. dep[v]=dep[u]+ 1;
  53. ++gap[dep[v]];
  54. }
  55. }
  56. }
  57. int SAP(int start,int end)
  58. {
  59. int res= 0;
  60. BFS(start,end);
  61. int cur[MAXN];
  62. int S[MAXN];
  63. int top= 0;
  64. memcpy(cur,head, sizeof(head));
  65. int u=start;
  66. int i;
  67. while(dep[start]<n)
  68. {
  69. if(u==end)
  70. {
  71. int temp=INF;
  72. int inser;
  73. for(i= 0;i<top;i++)
  74. if(temp>edge[S[i]].cap)
  75. {
  76. temp=edge[S[i]].cap;
  77. inser=i;
  78. }
  79. for(i= 0;i<top;i++)
  80. {
  81. edge[S[i]].cap-=temp;
  82. edge[S[i]^ 1].cap+=temp;
  83. }
  84. res+=temp;
  85. top=inser;
  86. u=edge[S[top]].from;
  87. }
  88. if(u!=end&&gap[dep[u] -1]== 0) //出现断层,无增广路
  89. break;
  90. for(i=cur[u];i!= -1;i=edge[i].next)
  91. if(edge[i].cap!= 0&&dep[u]==dep[edge[i].to]+ 1)
  92. break;
  93. if(i!= -1)
  94. {
  95. cur[u]=i;
  96. S[top++]=i;
  97. u=edge[i].to;
  98. }
  99. else
  100. {
  101. int min=n;
  102. for(i=head[u];i!= -1;i=edge[i].next)
  103. {
  104. if(edge[i].cap== 0) continue;
  105. if(min>dep[edge[i].to])
  106. {
  107. min=dep[edge[i].to];
  108. cur[u]=i;
  109. }
  110. }
  111. --gap[dep[u]];
  112. dep[u]=min+ 1;
  113. ++gap[dep[u]];
  114. if(u!=start)u=edge[S[--top]].from;
  115. }
  116. }
  117. return res;
  118. }

给边赋值时,养成习惯用加法,防止有重边!


   
  1. //****************************************************
  2. //最大流模板
  3. //初始化:g[][],start,end
  4. //******************************************************
  5. const int MAXN= 110;
  6. const int INF= 0x3fffffff;
  7. int g[MAXN][MAXN]; //存边的容量,没有边的初始化为0
  8. int path[MAXN],flow[MAXN],start,end;
  9. int n; //点的个数,编号0-n.n包括了源点和汇点。
  10. queue< int>q;
  11. int bfs()
  12. {
  13. int i,t;
  14. while(!q.empty())q.pop(); //把清空队列
  15. memset(path, -1, sizeof(path)); //每次搜索前都把路径初始化成-1
  16. path[start]= 0;
  17. flow[start]=INF; //源点可以有无穷的流流进
  18. q.push(start);
  19. while(!q.empty())
  20. {
  21. t=q.front();
  22. q.pop();
  23. if(t==end) break;
  24. //枚举所有的点,如果点的编号起始点有变化可以改这里
  25. for(i= 0;i<=n;i++)
  26. {
  27. if(i!=start&&path[i]== -1&&g[t][i])
  28. {
  29. flow[i]=flow[t]<g[t][i]?flow[t]:g[t][i];
  30. q.push(i);
  31. path[i]=t;
  32. }
  33. }
  34. }
  35. if(path[end]== -1) return -1; //即找不到汇点上去了。找不到增广路径了
  36. return flow[end];
  37. }
  38. int Edmonds_Karp()
  39. {
  40. int max_flow= 0;
  41. int step,now,pre;
  42. while((step=bfs())!= -1)
  43. {
  44. max_flow+=step;
  45. now=end;
  46. while(now!=start)
  47. {
  48. pre=path[now];
  49. g[pre][now]-=step;
  50. g[now][pre]+=step;
  51. now=pre;
  52. }
  53. }
  54. return max_flow;
  55. }



                          

                                                           


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值