网络流 笔记

news/2025/2/11 16:03:39/文章来源:https://www.cnblogs.com/Garbage-fish/p/18709926

本文原在 2024-07-22 10:17 发布于本人洛谷博客。

一、定义与性质

1. 基本定义

从水厂出发,有很多节点和水管,节点不能存水,但容量无限,水管有容量上限,全部水管最终经过某些节点都会流向某个工厂里,问最多同时能给工厂发多少水?

流网络:这张图。

源点:水厂。

汇点:工厂。

弧:水管。

弧的流量:这条水管当前流多少水,对于弧 \((u,v)\)\(f(u,v)\) 表示。

弧的容量:这条水管最多能流多少水,用 \(c(u,v)\) 表示。

弧的残量:这条水管还能流多少水,即 \(c(u,v)-f(u,v)\)

流量网络/容量网络/残量网络:边权表示/流量/容量/残量的流网络。

(重要) 增广路:在残量网络中,还能从源点流向汇点的一条路径。

2. 性质

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

  • 流量守恒:

    • 对于节点,输出量和输入量相等。
    • 源点的输出量等于汇点的输入量。

二、最大流

1. EK 算法

第一章第 1 节中所提到的问题就属于最大流问题。

基本思路就是:建一张容量网络,找到一条路径就给每条边减去相应的流量,并在剩下的残量网络上,不断找增广路。

但是对于下面这个图,显然找不到增广路了,但是肉眼可见最大流是 \(1\to 2\to 4\) 加上 \(1\to 3\to 4\)

因此给程序一个反悔的机会:给每条边都建一条反向边(根据 \(0\oplus 1 =1\)\(1\oplus 0 = 0\)\(2\oplus 1 = 3\)\(3\oplus 1 = 2\)\(4\oplus 1 =5\)……的性质,可以用 \(i\)\(i\oplus 1\) 作为一组相反的边),边权表示正向边的流量,实时更新。

每次找到一条路径后,给路径上的边减去本路径的最大流,路径上的反边加上本路径的最大流。这样就会发现:如果路径中含有一条反向边,它恰好能将正向边的容量给推回去。

int bfs() {  // bfs寻找增广路for (int i = 1; i <= n; i++)vis[i]=0;queue<int> q;q.push(s);vis[s] = 1;dis[s] = oo;while (!q.empty()) {int u = q.front();q.pop();for (int i = head[x]; i; i = edge[i].next) {if (edge[i].flow == 0)continue;// 如果是正向边:没有残量了怎么流?如果是反向边:正向边没有流量怎么流?int v = edge[i].v;if (vis[v])continue; // 访问过了flow[v] = min(flow[u], edge[i].flow);// 显然,一条路径的最大流取决于能流最小的那条边pre[v] = i; // 记录前驱,方便修改边权 q.push(v);vis[v] = 1;if (v == t)return 1; // 找到了一条增广路 }}return 0;
}void update() {  // 更新所经过边的正向边权以及反向边权 int u = t;while (u != s) {int v = pre[u];edge[v].flow -= flow[t];edge[v ^ 1].flow += flow[t];u = edge[v ^ 1].v;}maxflow += flow[t];   // 累加每一条增广路经的最小流量值 
}
void EK() {while (bfs())update();
}

2. Dinic 算法

EK 算法一次才找一条增广路,使用 Dinic 算法的 DFS 可以一次寻找多条增广路。

用 BFS 对图分层,作用在于一次可以得到多条长度相同的最短增广路。

每次从当前点出发,选用从当前点所在层到下一层的边,发送一定的流量,流量的大小取边残量和当前点从源点获取的剩余流中两者的最小值。

搜索完成后,返回一个流量值,即这条增广路的流量,此时就能够对边和反向边的残量进行更新了。

bool bfs() {fill(dis, dis + N, oo);queue<int> q;q.push(s);dis[s] = 0;now[s] = head[s];while (!q.empty()) {int u = q.front();q.pop();for (int i = head[u]; i; i = edge[i].next) {int v = edge[i].v;if (edge[i].w <= 0 or dis[v] != oo)continue;q.push(v);now[v] = head[v]; // 记录当前弧优化,可以不写,直接用 headdis[v] = dis[u] + 1; // 分层if (v == t) // 到汇点了return true;}}return false;
}
int dfs(int u, int last) {if (u == t) return last; // 到汇点了int ret = 0;for (int i = now[u]; i and last; i = edge[i].next) {now[u] = i;int v = edge[i].v;if (edge[i].w <= 0 or dis[v] != dis[u] + 1) // 没有流量或者层数对不上continue;int tmp = dfs(v, min(last, edge[i].w)); // 往后走的最大流if (!tmp) dis[v] = oo;edge[i].w -= tmp;edge[i ^ 1].w += tmp;ret += tmp;last -= tmp;}return ret;
}
void dinic() {while(bfs())ans += dfs(s, oo);
}

三、费用流

以最小费用最大流为例:

每条水管要收费,假设水管 \((u,v)\) 的单价是 \(cost\),那么你就要付 \(f(u,v)\times cost\)

将 EK 算法的 BFS 改成 SPFA 即可,可能有负边不能 Dijkstra。

namespace McMf {int cost[N], flow[N], pre[N];bool vis[N];bool spfa() {queue<int> q;fill(cost, cost + N, oo);memset(vis, 0, sizeof vis);q.push(s);cost[s] = 0;vis[s] = true;flow[s] = oo;while (!q.empty()) {int u = q.front();q.pop();vis[u] = false;for (int i = head[u]; i; i = edge[i].next) {if (!edge[i].flow)continue;int v = edge[i].v;if (cost[v] > cost[u] + edge[i].cost) {cost[v] = cost[u] + edge[i].cost;flow[v] = min(flow[u], edge[i].flow);pre[v] = i;if (!vis[v]) {q.push(v);vis[v] = true;}}}}return cost[t] <= oo / 2;}void mcmf() {while (spfa()) {int u = t;maxflow += flow[u];mincost += flow[u] * cost[u];while (u != s) {int p = pre[u];edge[p].flow -= flow[t];edge[p ^ 1].flow += flow[t];u = edge[p ^ 1].v;}}}
}
using namespace McMf;

四、上下界网络流

1. 无源汇上下界可行流

假设上下界是 \([L,R]\),点 \(x\) 流入的量是 \(in_x\),流出的量是 \(out_x\)

令每条边都先流自己的 \(L\),统计到 \(in_x\)\(out_x\) 中,然后构造一个流网络,边权为 \(R-L\)

如果 \(in_x>out_x\),那么建边 \((s,x,in_x-out_x)\)

如果 \(in_x<out_x\),那么建边 \((x,t,out_x-in_x)\)

在这个新构造的流网络中,如果源点流出的每一条边都能流满,那么说明流量平衡。

2. 有源汇上下界可行流

连接题目给出的源点 \((t,s,[0,\infty])\),用 1 的方法。

3. 有源汇上下界最大流

跑完可行流后,在残量网络中找 \(s\to t\) 的最大流。

#include <bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
using namespace std;
const int N = 1e6 + 10, oo = 1e18;
int n, m, s, t, s1, t1, in[N], out[N], over;
int head[N], ide = 1;
struct EDGE {int v, next, w;
} edge[N];
void add(int u, int v, int w) {edge[++ide] = {v, head[u], w};head[u] = ide;edge[++ide] = {u, head[v], 0};head[v] = ide;
}
int dis[N], now[N];
bool bfs() {fill(dis, dis + N, oo);queue<int> q;q.push(s);dis[s] = 0;now[s] = head[s];while (!q.empty()) {int u = q.front();q.pop();for (int i = head[u]; i; i = edge[i].next) {int v = edge[i].v;if (edge[i].w <= 0 or dis[v] != oo)continue;q.push(v);now[v] = head[v];dis[v] = dis[u] + 1;if (v == t)return true;}}return false;
}
int dfs(int u, int last) {if (u == t)return last;int ret = 0;for (int i = now[u]; i and last; i = edge[i].next) {now[u] = i;int v = edge[i].v;if (edge[i].w <= 0 or dis[v] != dis[u] + 1)continue;int tmp = dfs(v, min(last, edge[i].w));if (!tmp)dis[v] = oo;edge[i].w -= tmp;edge[i ^ 1].w += tmp;ret += tmp;last -= tmp;}return ret;
}
int dinic() {int ret = 0;while(bfs())ret += dfs(s, oo);return ret;
}
signed main() {IOS;cin >> n >> m >> s1 >> t1;s = n + 1, t = n + 2;for (int i = 1, u, v, lb, ub; i <= m; i++) {cin >> u >> v >> lb >> ub;in[v] += lb, out[u] += lb;add(u, v, ub - lb);}for (int i = 1; i <= n + 2; i++)if (in[i] > out[i]) {add(s, i, in[i] - out[i]);over += in[i] - out[i];} elseadd(i, t, out[i] - in[i]);add(t1, s1, oo);if (dinic() != over) {cout << "please go home to sleep";return 0;}s = s1, t = t1;cout << dinic();return 0;
}

4. 有源汇上下界最小流

求出可行流,把 \((t,s,[0,\infty])\) 删掉,再跑一次,前后两次相减。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/882124.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CentOS7 - firewalld只允许国内ip访问

firewalld只允许国内ip访问 #查看public区域大致情况 firewall-cmd --zone=public --list-all #获取china ip源 wget --no-check-certificate -O- http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest | awk -F\| /CN\|ipv4/ { printf("%s/%d\n", $4, 32…

2025 寒假集训 第二期

2025 寒假集训 第二期 J - Shift and Flip 题意:给出两个 \(01\) 串 \(A,B\) ,要求使两串相等,可以执行以下三种操作将 \(A\) 左移一个单位 将 \(A\) 右移一个单位 选择一个位置 \(i\) 满足 \(B_i=1\) ,使 \(A_i\) 取反求最小操作数。 思路:不可能的情况只有当 \(B\) 全为…

为word中的图片批量添加边框

写报告时,经常会在word中插入几十张甚至是上百张图片,而为了美观,通常会为这些图片添加边框,但一个个添加显然是疯狂且愚蠢的,我们应该做一些更有效率的事情。 使用python-docx来实现我们的操作(由于各种原因,不会选择用宏) 首先观察一下图片加边框前后的文档结构变化(…

DeepSeek本地化部署超简单!快给你的大模型安排上聊天助手吧!

上一篇我们讲了如何把deepseek R1 安装部署到个人电脑上。(感兴趣的请跳转至 https://www.cnblogs.com/AI2025/p/18709288) 但是,我们部署完了发现,如果仅仅是只在黑漆漆的命令行里面去和AI对话聊天,感觉很不人性化,也不好用。那么,有没有更好的和AI交互聊天的工具呢? …

prometheus监控k8s并发送报警

1.编辑prometheus的configmap文件kubectl edit cm prometheus-1738826520-server2.添加如下红色字体apiVersion: v1 data:alerting_rules.yml: |-groups:- name: deployment Monitoringrules:- alert: DeploymentReplicasUnavailableexpr: kube_deployment_status_replicas_una…

VS2022 安装失败 : 未能安装包“Microsoft.VisualStudio.JavaScript.SDK_1.0.1738743,version=1.0.1738743”

VS2022 安装失败,异常提醒: 1、未能安装包“Microsoft.VisualStudio.JavaScript.SDK_1.0.1738743,version=1.0.1738743” 2、未能安装包“Microsoft.Windows.SDK.BuildTools_10.0.26100.1742,version=1.0.0.0” 问题原因,可以直接查看本地安装异常日志文件,文件中的描述应该…

项目管理证书报考条件:不同证书的对比分析

项目管理领域的证书众多,它们在行业内都具有一定的认可度和价值。对于有意投身项目管理行业或提升自身项目管理能力的人来说,了解不同证书的报考条件至关重要。这不仅关系到能否顺利报考,更影响着未来的职业发展方向。接下来,我们将对几种常见的项目管理证书报考条件进行对…

C# 迷宫求解算法,给出思路和例子。

C# 迷宫求解算法 迷宫求解通常可以使用 深度优先搜索(DFS)、广度优先搜索(BFS)、A(A-star)搜索* 或 Dijkstra 算法。以下是几种常见方法的思路及代码示例。1. 迷宫表示 迷宫通常可以使用 二维数组(char[,] 或 int[,])来表示:0 表示可以通过的路径 1 表示墙或障碍物 S(…

No.18 Kappa系数精度评价2.0

# Loading necessary libraries library(openxlsx) library(vcd)# Reading the Excel data AccData <- read.xlsx("D:/R_proj/a绘图demo/bKappa/五指山生态系统分类精度评价一二级类.xlsx",sheet = 1, colNames = T)# Handle missing values (replace NA with 0) …

什么是回溯法,给个C#简单的例子。

回溯法(Backtracking)是一种搜索算法,主要用于解决组合优化问题,如全排列、子集、数独、八皇后问题等。它通过递归的方式尝试所有可能的解决方案,并在发现当前路径无法得到正确答案时回溯到上一步,继续尝试其他路径。 回溯法的基本思想选择:选择一个可能的选项。 约束:…

如何挑选项目型企业管理系统?10款软件为你解答

本文介绍了以下10款项目型企业管理系统:1.Worktile;2.PingCode;3.Teambition;4.石墨文档;5.蓝鲸智云;6.Trello;7.Asana;8.Monday.com;9.Jira;10.Basecamp。在日益复杂的项目管理环境中,很多企业面临一个共同的挑战:如何高效、精准地管理多个项目、协调各方资源,并…

【新零售】新零售ERP如何对接第三方WMS

一、新零售业务中仓储相关的业务场景 在新零售的业务模式中,由于涉及到的SKU种类巨多,同时门店覆盖的地域广泛,再加上新零售业态有多种多样的玩法(即时零售、全渠道一盘货等),所以会对仓储物流方面提出更多、更高的要求。 1.1 常规备货、发货的场景 新零售公司得从供应商…