一些网络流的基础模型

其实刚学网络流几天,菜的要命。。。

默认会前置芝士(DinicEK 等求解网络流算法)

最大流建模

最大流的建模角度是最直观的。

工程供需模型

存在一个有向图 \(G=(V,E)\),设 \(n=|V|,m=|E|\)。一条有向边 \((u,v,w)\) 表示能从节点 \(u\) 运货物到节点 \(v\) 且该道路的运输能力为 \(w\)。节点 \(i\) 有一个权值 \(p_i\)\(p_i\neq 0\)),\(p_i>0\) 表示这是一个工厂,可以产出 \(p_i\)\(p_i<0\) 表示这是一个消费者,可以消费 \(-p_i\)。工厂产出的货物可以沿着有向道路运送货物,运送到消费者手上时消费者就可以消费。求工厂能否满足所有消费者的消费需求。

做法

考虑把 \(p_i\) 转化到流上。我们建立超级源点 \(S\) 和超级汇点 \(T\),对于每个消费者,连接一条 \(i\to T\) 的容量为 \(-p_i\) 的有向边;对于每个工厂,连接一条 \(S\to i\) 的容量为 \(p_i\) 的有向边。对于有向边 \((u,v,w)\),将 \(w\) 当做容量。如果我们把 \(S\) 当做“生产总工厂”,流当做生产出的东西,就满足了题目条件。所有的产品从总工厂 \(S\) 沿着流流经工厂和消费者最后流到 \(T\)。由于消费者连接着 \(T\),所以产品一定会流进 \(T\) 的流一定流经消费者(这些产品一定能给消费者消费)。如果所以消费者连向 \(T\) 的边都满流了,那么就完成了所有消费者的供需。我们想要完成所有消费者的供需,肯定要尽量让流量大,那么就转化为了最大流,执行最大流算法即可。

二分图匹配

给定一个包含 \(n_1\) 个左部点,\(n_2\) 个右部点,\(m\) 条边的二分图。求最多能选出多少个不想交的边。

做法

考虑对于每个存在边的左部点 \(a\) 和右部点 \(b\) 连一条 \(a\to b\) 的容量为 \(1\) 的有向边。建立超级源点 \(S\) 和超级汇点 \(T\),对于每个左部点 \(a\) 连接一条 \(S\to a\) 的容量为 \(1\) 的有向边,对于每个右部点 \(b\) 连接一条 \(b\to T\) 的容量为 \(1\) 的有向边。此时我们求解从 \(S\)\(T\) 的最大流即为二分图最大匹配。考察这样做的正确性,由于每个左部点都被连了一条由源点连接的容量为 \(1\) 的有向边,所以从该点只会选择一条边流出 \(1\),选择了哪个相当于该点匹配了哪条边。此时最大流就是最大的流量,即为最大的选择的边数。

还有一些跟二分图最大匹配类似的(如圆桌聚餐),只是把容量改了改罢了,匹配逻辑还是一样的。

拆点建图

有时候,对于一条流流经的点的次数也有限制。由于网络流的容量只对边有限制作用,所以我们需要将一个点拆成一条 \(in_i\to out_i\) 有向边(当然有时候可以不连),所有连入这个点的有向边都连向这个点的 \(in_i\) 连边,所有从这个点连出的有向边都从 \(out_i\) 向外连出。

例题:最小路径覆盖

给出一张 DAG,可以选择 DAG 上的一些不相交的路径,求出最小需要几条路径才能覆盖所有

做法

一条路径显然是由多个点 合并 而成的,所以我们只需要求出最多能合并几次,那么就可以得到答案是 \(n-最大合并数\)。观察到一个点最多只能合并一次,不然合并出的就是一个 DAG 而非路径,那么我们考虑对每个点拆点,然后按照拆点建模的套路去建图,最后执行最大流算法即可求得最大合并数量。

注意,网络流做最小路径覆盖只能在 DAG 图上,遇到带环的一般图就会让网络流的流量限制出现一些 bug。所以一般图的最小路径覆盖实际为 NP-hard

根据转移关系建图

这个其实也没啥特别好说的,就是根据一些转移关系建立网络流,然后跑最大流即可。

例题:最长不下降子序列

给定一个序列,求出其最长不下降子序列的长度 \(s\),并求出每个数只能取一次的情况下最多能从序列中取出几个最长不下降子序列,再求出设第一个数和第 \(n\) 个数能取无限次的情况下最多能从序列中取出几个最长不下降子序列。

做法

考虑第一问,求出 \(f_i\) 表示以 \(i\) 为结尾的最长不下降子序列长度即可。然后考虑依据每个 \(a_j\leq a_i,f_j+1=f_i\)\(j\to i\) 的转移建立一条容量为 \(1\) 的有向边。由于每个数只能取一次,所以我们还要对每个节点额外拆点,将它们拆成一条 \(i\to i^{'}\) 的容量为 \(1\) 的有向边,所以上述 \(j\to i\) 相当于 \(j^{'}\to i\) 连边。再建立超级源点 \(S\),超级汇点 \(T\),让所有 \(f_i=1\)\(S\to i\) 的容量为 \(1\) 的有向边,所有 \(f_i=s\)\(i^{'}\to T\) 的容量为 \(1\) 的有向边。此时我们观察一个流,发现流出一个流相当于走完了一个最长不下降子序列,那么这张图的最大流实际上就是最多能走多少个最长不下降子序列,相当于第二问的答案。那么第三问就很简单了,我们只需将 \(S\to 1\)\(n^{'}\to T\) 的容量改成 \(\infty\),再执行最大流算法即可。

例题:魔术球

\(n\) 个柱子和很多的魔术球。你需要把魔术球按从编号 \(1\) 开始依次放到柱子上,如果柱子上已经有球了那么需要放在最顶端。若一个柱子上放了 \(>1\) 个求,那么需要满足这个柱子上所有相邻的求的编号之和都为 完全平方数。求最多能放几个球。

做法

观察到答案具有单调性,于是我们考虑二分答案。设每次二分到 \(mid\),我们考虑对编号为 \(1\sim mid\) 的球建立转移关系,即 \(i\to j\) 转移当且仅当 \(i+j\) 为完全平方数。然后此时这张图的 最小路径覆盖 就是所需要的最小的柱子数量,我们只需判断其是否 \(\leq n\) 即可。还有一个问题:最小路径覆盖只能在 DAG 图上用网络流做,我们还需要证明建出的转移图是 DAG。证明不难,这里略。

考虑这个二分好像没有上界,于是我们可以考虑倍增上界,在倍增的过程中进行二分。

实际上所有最大流建模的问题都是基于转移关系建立的。

最小割建模

首先,我们需要先证明 最大流最小割定理,实际上这也能反向证明最大流算法的正确性。此处证明不太严谨,可参考 oi-wiki 的证明。

设原图 \(G=(V,E)\)。我们定义源点为 \(S\),汇点为 \(T\),设两个集合 \(A,B\) 满足 \(S\in A,B\in T,A\cap B=\emptyset,A\cup B=V\)\(c_e\) 为边 \(e\) 的容量,\(f_e\) 为边 \(e\) 的流量。我们定义 \(A\to B\) 的净流量 \(F(A,B)=\sum_{e\in A\times B\cap E}f_e-\sum_{e\in B\times A\cap E}f_e\)。我们定义 \(A\to B\) 的“割”为 \(C(A,B)=\sum_{e\in A\times B\cap E}c_e\)。最小割即为 \(C(A,B)\) 的最小值。

由于满足 \(0\leq f_e\leq c_e\),那么我们可以得到 \(\max F(A,B)\leq\min C(A,B)\),即最大流 \(\leq\) 最小割。考虑执行网络流的过程:一直搜索增广路直到原图不存在增广路。此时所有 \(A\to B\) 的边都满流,反向边都空流,所以可以得到

\[\begin{align*} F(A,B) &= \sum_{e\in A\times B\cap E}f_e-\sum_{e\in B\times A\cap E}f_e\\ &= \sum_{e\in A\times B\cap E}c_e-\sum_{e\in B\times A\cap E} 0\\ &= C(A,B) \end{align*} \]

证得 \(最大流=最小割\)。于是求最小割时我们只需要对原图执行最大流算法即可。

工程选择问题 (最大权闭合子图)

\(n\) 个工程,执行第 \(i\) 个工程可以获得 \(a_i\) 的收益。但是还存在 \(m\) 种依赖关系,\(i\to j\) 表示执行工程 \(j\) 之前需要先执行工程 \(i\)。问在满足依赖关系的情况下最多能获得多少收益。

做法

其实最小割的建模一般都是推式子推出来的。

首先我们需要解决一个问题:\(a_i\) 可以是负数,这样就会把我们的网络流创飞。可以考虑对于一个 \(a_i\) 建立两个量 \(x_i,y_i\),满足:

  • \(a_i=x_i-y_i\)
  • \(a_i>0\) 时,\(x_i=a_i,y_i=0\)
  • \(a_i=0\) 时,\(x_i=y_i=0\)
  • \(a_i<0\) 时,\(x_i=0,y_i=|a_i|\)

然后考虑推答案式子。设 \(A\) 为选择做了工程的集合,\(B\) 为没做的集合。考虑 \(i\to j\) 的依赖关系怎么限制,我们可以设置一个“惩罚”,如果 \(i\) 没做但是 \(j\) 做了,那么就要扣除 \(\infty\) 的惩罚。

可以得到答案式子为

\[\begin{align*} ans &= \max\Big(\sum_{i\in A}a_i-\sum_{e\in B\times A\cap E}\infty\Big)\\ &= \max\Big(\sum_{i\in A}x_i-\sum_{i\in A}y_i-\sum_{e\in B\times A\cap E}\infty\Big)\\ &= \max\Big(\sum_{i\in V}x_i-\sum_{i\in B}x_i-\sum_{i\in A}y_i-\sum_{e\in B\times A\cap E}\infty\Big)\\ &= \sum_{i\in V}x_i-\min\Big(\sum_{i\in B}x_i+\sum_{i\in A}y_i+\sum_{e\in B\times A\cap E}\infty\Big) \end{align*} \]

发现此时括号内的式子已经包含 \(A\) 项,\(B\) 项和 \(AB\) 的交叉项了,于是我们对后面的 \(\min\) 里的式子建图,求解最小割即可。建图的时候满足 \(S\in A,T\in B\),所以 \(i\to T\) 的边相当于将 \(i\) 划分进 \(A\) 的代价,\(S\to i\) 的边相当于将 \(i\) 划分进 \(B\) 的代价,\(i\to j\) 的边相当于把 \(i,j\) 划分进不同集合的代价。此时令 \(S\to i\) 的边权为 \(x_i\)\(i\to T\) 的边权为 \(y_i\)\(i\to j\) 的边权为 \(\infty\) 即可,然后跑最小割就能求得最小代价。

例题. 最大获利

做法

考虑将 \(A_i,B_i\) 连接也当做一个工程,对其建立虚拟节点,然后按照最小割的套路连边建图即可。

posted @ 2026-01-27 20:14  DeadFatsheep  阅读(11)  评论(0)    收藏  举报