网络流
本文章遵守知识共享协议 CC-BY-NC-SA ,转载时须在文章的任一位置附上原文链接和作者署名(rickyxrc)。推荐在我的个人博客阅读。
update:2022/12/6 增加一道题
网络流
前置知识
- 图
- 深度优先搜索
- 广度优先搜索
网络流长什么样?
网络流的图一般是这种图:
每条边的最大流量如下图所示,求从4到3的最大流量。
仔细思考一下,可以得出最大流量应该是 50
,每条边的实际流量应该是这样:
你的解法可能和我的不一样,类似于这样:
网络流的性质
但是无论如何,一个可行的流应该满足如下条件:
- 容量约束
这个很容易理解,所有边的实际流量不能超过容量。
- 反对称性
假设 u
到 v
的流量是 ,则从 v
到 u
的流量是 或 。
- 流量守恒
和电路有点像(随便在电路里画个圈,流入电流等于流出电流)。
对于除 以外任意一个点,流入流量等于流出流量。
更形式化的说,
用上面图中的一部分举例:
讲解
知道了这些性质,我们就应该求解网络最大流了。
EK算法
EK算法是一种非常简单且朴素的用于求解网络流的算法,需要使用深度优先搜索
和广度优先搜索
。
主要步骤如下:
- 找增广路
- 增广
- 回到步骤1,直到找不到增广路为止
这里引入一个网络流中十分重要的概念:反向边。
反向边具有如下规则:一条边增流时,反向边减流,反之亦然。
为什么要这样呢?因为我们的 bfs
第一次搜索出的路线可能不是最优的,所以我们需要反向边来给程序一个后悔的机会。
让我们模拟一遍,再次回到开始时的那张图:
我们首先 bfs
,找到一条路径: 4->3
。
正向边减流,反向边增流,最大流变为20,图变成了这样:
此时反向边的作用就出来了,沿着反向边向回搜索,正向边增流,反向边减流,回到了搜索前的状态(这就是所谓反悔)。
多次重复这样的操作,直到原点与汇点不再联通为止。
甚至核心代码只有一行
while(bfs())update();
这是极其朴素的算法,但是这样的算法仍然存在许多缺点,比如每次都判断是否联通太过浪费,搜索方向不明确等等。
贴上bfs
的代码:
bool bfs(){ memset(vis,0,sizeof(vis)); queue<int> q; q.push(s),vis[s]=1; incf[s]=inf; // 源点流量无限 while(q.size()){ int u=q.front();q.pop(); for(int i=head[u];i;i=Next[i]){ if(c[i]){ int v=ver[i]; if(vis[v])continue; // 不要重复添加 incf[v]=min(incf[u],c[i]); // 这条链 (下一个节点到当前节点)最大流量应该为路径上最小 pre[v]=i; // 设置当前节点的前一个 q.push(v),vis[v]=1; // 继续搜索 if(v==t) return 1; // 找到就退出,并标记找到 } } } return 0; // 没找到就凉拌 } void update(){ int u=t; // 反向找回去,从t回到s while(u!=s){ int i=pre[u]; // 上一个 c[i]-=incf[t]; // 反向边减流 c[i^1]+=incf[t]; // 正向边增流 u=ver[i^1]; // 实际上是正向边的上一个 } maxflow+=incf[t]; // 更新最大流 }
dinic
前面提到, EK
算法缺点是每次都 dfs
,效率太低,所以我们需要 dinic
算法。
dinic
通过一次 bfs
可以实现多次增流,还可以避免扫描重复。
相对于EK算法,dinic的重复搜索更少,效率应该会更高。
bool bfs(){ memset(d,-1,sizeof(d)); queue<int> q; q.push(s),d[s]=0,cur[s]=head[s]; while(q.size()) { int u = q.front(); q.pop(); for(int i = head[u]; i; i = Next[i]) { // 遍历每条出边 int v= ver[i]; if(d[v] == -1 && c[i]) { q.push(v); // 添加到队列 d[v] = d[u] + 1; // 更新深度 cur[v] = head[v]; if(v==t)return true; // 搜索到终点就退出 } } } return false; // 没搜索到就退出并标记失败 } int dfs(int u, int limit) { if(u==t) return limit; // 到达就返回 int flow = 0; for(int i=cur[u];i&&flow<limit;i=Next[i]){ // 遍历 cur[u]=i; int v=ver[i]; if(c[i]!=0&&d[v]==d[u]+1){ // 沿分层图深度+1方向搜索 int f=dfs(v,min(c[i],limit-flow)); // 分配最大流 if(!f)d[v]=-1; // c[i]-=f,c[i^1]+=f,flow+=f; // 正向边减流,反向边增流 } } return flow; // 返回最大流 } int dinic(){ int maxflow=0,flow=0; while(bfs()) // 判断联通并分层 while(flow=dfs(s,inf)) // 还有增流的空间(一次bfs多次增流) maxflow+=flow; // 增流 return maxflow; }
例题
P3376-【模板】网络最大流
P2936-[USACO09JAN]Total-Flow-S
P2740-[USACO4.2]草地排水Drainage Ditches
照例模板题。
P3386-【模板】二分图最大匹配
这道题也是一个网络流,转化问题,新建一个源点和一个汇点,将源点连接左侧点,所有右侧点连接汇点,且所有边的容量均为1,跑一遍最大流就行。
这种问题需要多转化,多练。
P2756-飞行员配对方案问题
和上一道题思路非常像,但是多了一个输出匹配的过程。
这里如果你使用 dinic
算法,只需要看源点与这个点是否联通,因为在最后的流量图中,被增流的点一定不与源点联通。
P1231-教辅的组成
本质是一道最大匹配问题
首先,我们可以构建这样一个图,流向就是 源点->练习册->答案->汇点
。
但是这种图有一个问题,就是节点会被复用,不能保证书只匹配一个。
所以我们这里使用到了一个拆点的思想,将一个点拆成两个点。
scanf("%d%d%d", &n1, &n2, &n3); s = 0, t = 2*n1 + n2 + n3 + 1; for (int i = 1; i <= n1; i++) add(n2+i,n1+n2+i,1); for (int i = 1; i <= n2; i++) add(s, i, 1); for (int i = 1; i <= n3; i++) add(2*n1+n2+i, t, 1); scanf("%d", &m1); for (int i = 1; i <= m1; i++) { scanf("%d %d", &u, &v); add(v,n2+u,1); } scanf("%d", &m2); for (int i = 1; i <= m2; i++) { scanf("%d %d", &u, &v); add(n2+n1+u,n2+2*n1+v,1); } printf("%d",dinic());
P1402-酒店之王
和上一道题同理。
P2763-试题库问题
这道题目也是一个最大匹配问题。
源点连接每道试题,试题连接题型,这些部分的容量都为1。
每种题型连接汇点,容量为题目中每组题选择的数量。
scanf("%d%d",&k,&n); s=0; t=n+k+1; for (int i=1;i<=k;i++) { scanf("%d",&tmp); add(n+i,t,tmp); // 题型连汇点 } for (int i=1;i<=n;i++) { scanf("%d",&num); for (int j=1;j<=num;j++) { scanf("%d",&tmp); add(i,n+tmp,1); // 试题连题型 } } for (int i=1;i<=n;i++) add(s,i,1); // 源点连试题 dinic(); // 求解最大流 for(int i=1;i<=k;i++){ printf("%d:",i); for (int tm = head[n+i]; tm; tm = Next[tm]){ if(ver[tm] != t && c[tm^1] == 0) // 反向边流量为0(这条边被使用了) // 不能输出汇点!!! printf(" %d",ver[tm]); // 输出节点编号 } putchar('\n'); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用