Luogu P3376

最大流是网络流模型的一个基础问题。

网络流模型就是一种特殊的有向图。

概念:

  • 源点:提供流的节点,类比成为一个无限放水的水厂
  • 汇点:接受流的节点,类比成为一个无限收水的小区
  • 弧:类比为水管
  • 弧的容量:类比为水管的容量;用函数\(c(x,y)\)表示弧\((x,y)\)的容量
  • 弧的流量:类比为当前在水管中水的量;用函数\(f(x,y)\)表示弧\((x,y)\)的流量
  • 弧的残量:即容量-流量
  • 容量网络:对于一个网络流模型,每一条弧都给出了容量,则构成一个容量网络。
  • 流量网络:对于一个网络流模型,每一条弧都给出了流量,则构成一个流量网络。
  • 残量网络:对于一个网络流模型,每一条弧都给出了残量,则构成一个残量网络。最初的残量网络就是容量网络。

对于网络流模型\(G=(V,E)\)(\(V\)为点集,\(E\)为边集)有如下性质:

  • 流量守恒:除了源点与汇点之外,流入任何节点的流一定等于流出该节点的流
  • 容量限制:\(\forall (x,y) \in E,有0<=f(x,y)<=c(x,y)\)
  • 斜对称性:\(\forall (x,y) \in E,有f(x,y)=-f(y,x).\)类似于函数奇偶性中的奇函数,或者是矢量的方向。

最大流问题,用通俗的方式解释就是从源点S到汇点T输送流量,询问最多有多少流量能输送到汇点。

对于这样的问题,我们引入一些新概念:

  • 增广路:一条从源点到汇点的路径\(R\),满足\(\forall (x,y) \in R, c(x,y)-f(x,y)>0.\)即残量为正数
  • 最大流最小割定理:网络流模型达到最大流,当且仅当残量网络中没有任何增广路(并不完整,但是足够了)

\(Ford-Fulkerson\)方法:每一次寻找一条增广路径。根据木桶原理,该增广路的最大流量\(f_{max}<=min(c(x,y)-f(x,y))\)。据此从源点发送流量至汇点并修改路径上所有弧的残量,直到无法找到增广路为止。

\(Edmons-Karp\)算法:基于\(Ford-Fulkerson\)方法的一种算法,核心就是利用\(BFS\)搜索源点到汇点的最短增广路,根据\(Ford-Fulkerson\)方法修改残量网络。复杂度最坏是\(O(nm^2)\)

所以其实我们在求最大流相关问题时,其实只利用到了残量网络,流量和容量一般并不需要记录。

关键点:有时候在求最大流时我们可能需要缩减一条边的流量,所以我们引入了反向边。当我们选用了一条反向边时,相当于缩减正向边的流量。很容易发现反向边的残量等于正向边的流量(最多恰好抵消正向流量)。

这样就能保证算法的正确性。

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<queue>
  4. #include<cstring>
  5. using namespace std;
  6. struct data
  7. {
  8. int to,next,val;
  9. }e[2*100005];
  10. int cnt,head[10005],prep[10005],pree[10005],flow[10005],ans;
  11. queue<int> que;
  12. int n,m,s,t,u,v,w;
  13. void add(int u,int v,int w)
  14. {
  15. e[++cnt].to=v;
  16. e[cnt].next=head[u];
  17. head[u]=cnt;
  18. e[cnt].val=w;
  19. }
  20. int bfs(int s,int t)
  21. {
  22. while (!que.empty()) que.pop();
  23. flow[s]=0x3f3f3f3f;//flow记录的是在增广路上经过该点的流量
  24. que.push(s);
  25. for (int i=1;i<=n;i++)
  26. {
  27. prep[i]=-1;//用于记录前驱节点
  28. pree[i]=0;//用于记录前驱边的编号
  29. }
  30. prep[s]=0;
  31. while (!que.empty())
  32. {
  33. int now=que.front();
  34. que.pop();
  35. if (now==t) break;
  36. for (int i=head[now];i;i=e[i].next)
  37. {
  38. if (e[i].val>0&&prep[e[i].to]==-1)
  39. {
  40. que.push(e[i].to);
  41. flow[e[i].to]=min(flow[now],e[i].val);
  42. pree[e[i].to]=i;
  43. prep[e[i].to]=now;
  44. }
  45. }
  46. }
  47. if (prep[t]!=-1) return flow[t];
  48. else return -1;
  49. }
  50. void EK(int s,int t)
  51. {
  52. int delta=bfs(s,t);//寻找最短增广路的最大流量
  53. while (delta!=-1)
  54. {
  55. ans+=delta;
  56. for (int j=t;j;j=prep[j])
  57. {
  58. e[pree[j]].val-=delta;
  59. e[pree[j]^1].val+=delta;
  60. //链式前向星存边从编号2开始存储可以通过异或1快速取得反向边的编号。
  61. }
  62. delta=bfs(s,t);
  63. }
  64. }
  65. int main()
  66. {
  67. scanf("%d%d%d%d",&n,&m,&s,&t);
  68. cnt=1;
  69. for (int i=1;i<=m;i++)
  70. {
  71. scanf("%d%d%d",&u,&v,&w);
  72. add(v,u,0);
  73. add(u,v,w);
  74. //加入正反边
  75. }
  76. EK(s,t);
  77. printf("%d",ans);
  78. return 0;
  79. }

Luogu P3381

假设现在对每一条弧加入一个费用,表示该弧单位流量需要的费用,要求最小费用最大流。

那么这就是费用流问题。

事实上并不难,对EK算法稍作修改,将EK中BFS寻找最短增广路改成SPFA寻找最小费用的增广路即可

  1. #include<cstdio>
  2. #include<queue>
  3. using namespace std;
  4. const int maxn=5005,maxm=50005,inf=0x3f3f3f3f;
  5. struct data
  6. {
  7. int to,next,val,pri;
  8. }e[2*maxm];
  9. int cnt,tot,ans,head[maxn],n,m,s,t,u,v,w,f,cost[maxn],prep[maxn],pree[maxn],flow[maxn];
  10. void add(int u,int v,int w,int f)
  11. {
  12. e[++cnt].to=v;
  13. e[cnt].next=head[u];
  14. e[cnt].val=w;
  15. e[cnt].pri=f;
  16. head[u]=cnt;
  17. }
  18. queue<int> que;
  19. int vis[maxn];
  20. int SPFA(int s,int t)
  21. {
  22. for (int i=1;i<=n;i++)
  23. {
  24. cost[i]=inf;
  25. prep[i]=-1;
  26. pree[i]=0;
  27. flow[i]=inf;
  28. //初始化容易漏
  29. }
  30. cost[s]=0;
  31. que.push(s);
  32. vis[s]=true;
  33. prep[s]=0;pree[s]=0;flow[s]=inf;
  34. while (!que.empty())
  35. {
  36. int now=que.front();
  37. que.pop();
  38. vis[now]=false;
  39. for (int i=head[now];i;i=e[i].next)
  40. {
  41. if (e[i].val>0&&cost[e[i].to]>cost[now]+e[i].pri)
  42. {
  43. cost[e[i].to]=cost[now]+e[i].pri;
  44. flow[e[i].to]=min(flow[now],e[i].val);
  45. prep[e[i].to]=now;
  46. pree[e[i].to]=i;
  47. if (!vis[e[i].to])
  48. {
  49. vis[e[i].to]=true;
  50. que.push(e[i].to);
  51. }
  52. }
  53. }
  54. }
  55. if (prep[t]!=-1) return flow[t];
  56. else return -1;
  57. }
  58. void EK(int s,int t)
  59. {
  60. int delta=SPFA(s,t);
  61. while (delta!=-1)
  62. {
  63. ans+=delta;tot+=delta*cost[t];
  64. for (int j=t;j;j=prep[j])
  65. {
  66. e[pree[j]].val-=delta;
  67. e[pree[j]^1].val+=delta;
  68. }
  69. delta=SPFA(s,t);
  70. }
  71. }
  72. int main()
  73. {
  74. scanf("%d%d%d%d",&n,&m,&s,&t);
  75. cnt=1;
  76. for (int i=1;i<=m;i++)
  77. {
  78. scanf("%d%d%d%d",&u,&v,&w,&f);
  79. add(u,v,w,f);
  80. add(v,u,0,-f);//反向边的费用取相反数
  81. }
  82. EK(s,t);
  83. printf("%d %d",ans,tot);
  84. return 0;
  85. }

【Luogu P3376】网络最大流的更多相关文章

  1. 【luogu P3376 网络最大流】 模板

    题目链接:https://www.luogu.org/problemnew/show/P3376 #include <iostream> #include <cstdio> # ...

  2. 【Luogu】P3376网络最大流模板(Dinic)

    最大流模板成为另一个被攻克的模板题. 今天QDC给我讲了一下Dinic,感觉很好懂.于是为了巩固就把这道题A掉了. 核心思想就是不断BFS分层,然后不断DFS找增广路.找不到之后就可以把答案累加输出了 ...

  3. P3376 网络最大流模板(Dinic + dfs多路增广优化 + 炸点优化 + 当前弧优化)

    ### P3376 题目链接 ### 这里讲一下三种优化的实现以及正确性. 1.dfs多路增广优化 一般的Dinic算法中是这样的,bfs() 用于标记多条增广路,以至于能一次 bfs() 出多次 d ...

  4. P3376 【模板】网络最大流(luogu)

    P3376 [模板]网络最大流(luogu) 最大流的dinic算法模板(采取了多种优化) 优化 时间 inline+当前弧+炸点+多路增广 174ms no 当前弧 175ms no 炸点 249 ...

  5. luogu P3376 【模板】网络最大流(no)ek

    题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行 ...

  6. Dinic最大流 || Luogu P3376 【模板】网络最大流

    题面:[模板]网络最大流 代码: #include<cstring> #include<cstdio> #include<iostream> #define min ...

  7. P3376 【模板】网络最大流

    P3376 [模板]网络最大流 题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点 ...

  8. P3376 【模板】网络最大流dinic算法

    P3376 [模板]网络最大流 题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点 ...

  9. 『题解』洛谷P3376 【模板】网络最大流

    Problem Portal Portal1:Luogu Description 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. Input 第一行包含四个正整数\(N,M,S,T\),分 ...

随机推荐

  1. oc基本知识

    (一)构造函数 h文件 #import <Foundation/Foundation.h> @interface Student : NSObject { NSString *_name; ...

  2. Python语法入门02

    引子 上一篇我们主要了解到了python这门编程语言,今天来说一下关于用户交互,数据类型和运算符方面的学习内容 用户交互 什么是用户交互? 用户交互就是人往计算机里输入数据(input),计算机输出结 ...

  3. java中的时区转换

    目录 java中的时区转换 一.时区的说明 二.时间的表示 三.时间戳 四.Date类和时间戳 五.java中的时区转换 java中的时区转换 一.时区的说明 地球表面按经线从东到西,被划成一个个区域 ...

  4. 【Java必修课】HashMap性能很好?问过我EnumMap没

    1 简介 我们知道Map只是一个接口,它有多种实现,Java中最常用的是HashMap了.而本文想讲述的是另一个实现:EnumMap.它是枚举类型的Map,要求它的Key值都必须是枚举型的. 2 创建 ...

  5. 前端技术之:常见前端UI相关开源项目

    Bootstrap https://getbootstrap.com/BootstrapVue provides one of the most comprehensive implementatio ...

  6. Linux中的快捷方式

    history 显示命令历史列表 ↑(Ctrl+p) 显示上一条命令 ↓(Ctrl+n) 显示下一条命令 !num 执行命令历史列表的第num条命令 !! 执行上一条命令 !?string? 执行含有 ...

  7. Bash 通配符、正则表达式、扩展正则表达式

    BASH中的通配符(wildcard) *:任意长度的任意字符. ?:任意单个字符 []:匹配范围 [^]:排除匹配范围 [:alnum:]:所有字母和数字 [:alpha:]:所有字母 [:digi ...

  8. 入门Android底层需要的一些技能

    <Android的设计与实现> Android框架层<Linux系统编程手册> Linux系统编程<Android内核剖析> 编译框架和romC语言和Linux内核 ...

  9. [考试反思]1108csp-s模拟测试105: 傀儡

    评测机是真的老了... 我的脑力也老了... 昨天写完T3之后感觉脑子就留在那了,直到现在还感觉自己神志不清... T1OJ上过了(跑得挺慢但是的确过了),但是文件评测同样是开O2居然只剩下70分.. ...

  10. NOIP模拟测试14

    考完19了再写14,我也是够咕的. 14的题很好,也充分暴露了我的问题. T1是个分析性质推结论的题 对于区间[L,R],不妨设a[L]!=a[R],那么两个端点对答案没有贡献,也就是[L+1,R], ...