网络流与二分图学习笔记

咋忘搬这个了

部分仅总结不展开。参考 Alex-wei的博客

网络流

1.最大流

最大流最小割定理

最大流 = 最小割。

证明:

  • 任意流 \(\le\) 任意割。(任意流为整个图最后流量汇聚的水流,而任意割为割掉一些水管使任意水流都无法到达终点。任意割取决于割边的流量限制,而任意流的流量限制还包括其他边,限制可能更紧,且在限制下还不一定流满。)
  • 对于任意最大流构造割,使得割等于最大流

残留网络中源可以到的点集为 \(S\),不能到的点集为 \(T\),取割 \(C(S,T)\)。(可以想象为:从 \(S\) 点出发,断开的那些边就是流量流满的边,那些边是第一个在全局上对流量起决定性限制作用的边,这些边相加就是最大流)

  • 设这个最大流为 \(F_m\),割为 \(C_m\),根据第一条,对于任意割 \(C\)

\[F \le F_m = C_m \le C \]

Dinic

最大流常规做法。

流程是:

  1. 每次增广前先 bfs 给图分层,如果到不了 \(t\) 则最大流求完了。
  2. dfs 进行多路增广,一层一层往下。注意 dfs 的顺序要满足之前 bfs 的分层。注意要加弧优化,否则时间复杂度退回到和 EK 一样了。

单次增广时间复杂度 \(O(nm)\)

点击查看代码

注意第41行和25行是容易落下的剪枝部分。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 210,M = 10010;
int n,m,s,t,tote;
int hed[N],nxt[M],to[M],w[M],dep[N],cur[N];
queue<int> q;
void addedge(int x,int y,int z){
	tote++,nxt[tote] = hed[x],to[tote] = y,w[tote] = z,hed[x] = tote;
}
bool bfs(){
	memset(dep,-0x3f3f3f3f,sizeof(dep)),dep[s] = 0,q.push(s);
	while(!q.empty()){
		int x = q.front();q.pop();
		for(int i = hed[x]; i; i = nxt[i]){
			int y = to[i];
			if(dep[y] < 0 && w[i])dep[y] = dep[x] + 1,q.push(y);
		}
	}
	return dep[t] >= 0;
}
int dfs(int now,int lm){
	if(now == t)return lm;
	int ans = 0;
	for(int i = cur[now]; i && lm; i = cur[now]){//!!记得判lm是否还有
		//cout << cur[now] << " " << to[now] << endl;
		cur[now] = nxt[i];
		int x = to[i];
		if(dep[x] == dep[now] + 1 && w[i]){
			int k = dfs(x,min(lm,w[i]));
			if(!k)dep[x] = -1e9;
			else w[i] -= k,w[i ^ 1] += k,lm -= k,ans += k;
		}
	}
	return ans;
}
int dinic(){
	int flow = 0,ans = 0;
	while(bfs()){
		for(int i = 1; i <= n; i++)cur[i] = hed[i];
		while(1){//!!一次分层可以增广多次
			flow = dfs(s,1e18);
			if(!flow)break;
			ans += flow;
		}
	}
	return ans;
}
signed main(){
	tote = 1;
	scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
	for(int i = 1,x,y,z; i <= m; i++){
		scanf("%lld%lld%lld",&x,&y,&z);
		addedge(x,y,z),addedge(y,x,0);
	}
	printf("%lld\n",dinic());
	return 0;
}

2.无负环费用流

每条边的单位流量要一定费用。在实现网络最大流的基础上让费用最小。

SSP

基于 EK 的求法。流程:

  1. 将 EK 的 bfs 换成 SPFA,边的长度为单位流量的费用。在 SPFA 的过程中把流量记了,把更新路径也记下来。注意这里 SPFA 要完整跑完,不要队列里拿到 \(t\)break,因为此时的 \(dis_t\) 不一定已经更新完了。
  2. 沿着之前记的路径从 \(t\) 反推回 \(s\),把路径上的流量变化更新一下。答案加上这次增广所花的费用和增加的流量。
  3. 多次增广,直到 SPFA 不通为止。

时间复杂度 \(O(nmf)\)\(f\) 为网络流中的最大流量。但是一般 \(f\) 跑不满,\(nm\) 也跑不满,上界比较松。

OI 界一般以 dinic 作为网络最大流的标准算法,以基于 EK 的 SSP 作为费用流的标准算法。「最大流不卡 dinic,费用流不卡 EK」是业界公约。

点击查看代码
//EK
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5010,M = 100010;
int n,m,s,t;
int hed[N],to[M],nxt[M],w[M],cst[M],tote;
int id[N],dis[N],vis[N],fl[N],fr[N];
void addedge(int x,int y,int z,int c){
	tote++,nxt[tote] = hed[x],to[tote] = y,w[tote] = z,cst[tote] = c,hed[x] = tote;
}
queue<int> q;
int spfa(){
	memset(dis,0x3f3f3f3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	memset(fl,0,sizeof(fl));
	dis[s] = 0,q.push(s),vis[s] = 1,fl[s] = 1e18;
	while(!q.empty()){
		int x = q.front();q.pop(),vis[x] = 0;
		for(int i = hed[x]; i; i = nxt[i]){
			int y = to[i];
			if(dis[y] > dis[x] + cst[i] && w[i]){
				dis[y] = dis[x] + cst[i],fr[y] = x;
				fl[y] = min(fl[x],w[i]),id[y] = i;
				if(!vis[y])q.push(y),vis[y] = 1;
			}
		}
	}
	return fl[t];
}
pair<int,int> EK(){
	int anscst = 0,ansfl = 0,flow = 0;
	while(1){
		flow = spfa();
		if(!flow)break;
		ansfl += flow,anscst += flow * dis[t];
		int k = t;
		while(k != s){
			w[id[k]] -= flow,w[id[k] ^ 1] += flow,k = fr[k];
		}
	}
	return {ansfl,anscst};
}
signed main(){
	tote = 1;
	scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
	for(int i = 1,x,y,z,c; i <= m; i++){
		scanf("%lld%lld%lld%lld",&x,&y,&z,&c);
		addedge(x,y,z,c),addedge(y,x,0,-c);
	}
	auto ans = EK();
	printf("%lld %lld",ans.first,ans.second);
	return 0;
}

原始对偶

咕咕咕。

3.上下界网络流

咕咕咕。

4.网络流的应用

本质上,网络流是一种可反悔贪心。题目中常常利用网络流建图,用流量分配/割边表示一种方案。

割边作为方案

跑完最大流后,bfs 部分自然保留了最后一次每个点的 dis。设残留网络中源可以到的点集为 \(S\),不能到的点集为 \(T\),dis 有值的就属于 \(S\),没值的属于 \(T\)。取割 \(C(S,T)\)

注意不是所有流满的边都是割边。

动态加边问题

因为网络流算法本身自带反悔操作,所以在解决动态加边的 最大流 问题时,我们不需要担心原来的流方案会影响到算法求解新图最大流时的正确性。

但对于 费用流,因为其正确性依赖于每一步增广路均为最短路,所以一旦给网络加入新边,就必须重新跑费用流才能得到正确费用。

二分图

二分图就是可以将原图点集分成两部分,满足两个点集内部没有边的图。

众多二分图问题可以用网络流解决。

二分图典型问题

二分图最小顶点覆盖

问题:给定一个二分图,现在你要选出一些点,使得每条边至少有一个端点被选出,求最少要选多少个点(这个点集被称为覆盖集),\(n \le 10^5\)

\(S\)\(X_i\) 连容量 \(1\) 的边,\(Y_j\)\(T\) 连容量 \(1\) 的边,\(X_i\)\(Y_j\) 连容量 \(\infty\) 的边。

中间边的边权问题

下面证明 Kőnig 定理 后,可以发现 \(X_i\)\(Y_j\) 连容量为 \(1\) 的边也是正确的。感性理解:两边的边更稀疏,割中间的边不如割两边的边划算,一定存在没有割中间边的方案。

考虑该图一个容量小于 \(\infty\) 的割,每条割边都是 \((S,X_i)\) 或是 \((Y_j,T)\),取这些 \(X_i,Y_j\) 作为覆盖集。

如果边 \((X_i,Y_j)\) 未被覆盖,这意味着 \((S,X_i)\)\((Y_j,T)\) 都不是割边,即存在 \(S-T\) 路径,与假设矛盾。所以以上方法正确。

建图,跑个最大流,用一下最大流最小割定理即可。

最大匹配

问题:二分图中选一些边,这些边的起点与终点互不相同。求选边的最大数量。

\(S\)\(X_i\) 连容量 \(1\) 的边,\(Y_j\)\(T\) 连容量 \(1\) 的边,\(X_i\)\(Y_j\) 连容量 \(\infty\) 的边(边权取 1 好像也行?)。然后跑最大流就是答案。

这个构图怎么和 二分图最小顶点覆盖模型 一模一样?所以有结论:二分图最小顶点覆盖 = 最大匹配(Kőnig 定理)

二分图最大独立集

问题:给定一个二分图,现在你要选出一些点,使得这些点两两之间没有边相连,求最多能选出多少个点(这个点集被称为独立集),\(n \le 10^5\)

结论:最小顶点覆盖 \(+\) 最大独立集 \(=\) 总点数,且这个结论在一般图上也成立

证明如下:将独立集从点集中删去,剩下的点集一定是个覆盖集。

否则有一条边的两个端点都被删除,而这两个端点不可能有边(独立集定义),即出现矛盾。于是又变成了最小顶点覆盖问题。

【拓】一般图的概念与结论

对于图 \(G=(V,E)\)

  • 匹配:\(G\) 中两两没有公共端点的边集合 \(M \subseteq E\)

  • 边覆盖:\(G\) 中任意顶点都至少是 \(F\) 中某条边的端点的边集合 \(F \subseteq E\)(用边覆盖全图点)

  • 独立集:在 \(G\) 中两两互不相连的顶点集合 \(S \subseteq V\)

  • 顶点覆盖:\(G\) 中的任意边都至少有一个端点属于 \(S\) 的顶点集合 \(S \subseteq V\)(用点覆盖全图边)

  • 对于不存在孤立点的图:最大匹配 \(+\) 最小边覆盖 \(=\) 总点数

证明

设最大匹配 \(= p\),最小边覆盖 \(= v\),总点数 \(=n\)

最大匹配中包含了 \(2p\) 个互不相同的点,剩余 \(n-2p\) 个点未连,再额外连 \(n-2p\) 条边一定能使这些点被覆盖。所以有 \(p + n-2p = n-p \ge v\)

反过来,考虑用最小边覆盖构造一种匹配。最小边覆盖中一定不会有长度 \(\ge 3\) 的链,因为如果存在链 甲—乙—丙—丁,那么去掉 乙-丙 边显然还是成立,不符合“最小”要求。因此,最小边覆盖的图应该长成“星星”状,由一个点向外辐射连边,而这些“星星”不会相交。每个“星星”包含点数为边数 + 1,设“星星”个数为 \(k\),所以 \(v + k=n\)\(k=n-v\)

每个“星星”各选一条边,一定构成一种合法匹配。即 \(p \ge k = n-v\)\(n-p \le v\)

两式结合有 \(v+p=n\)

  • 最大独立集 \(+\) 最小顶点覆盖 \(=\) 总点数

  • 二分图中,最小顶点覆盖等于最大匹配,最大独立集等于最小边覆盖。

最大权闭合子图问题

一张有向图,点有点权可正可负,选出一张子图,满足子图中的所有点出度指向的点依旧在这个子图内,则此子图是闭合子图。

一个例题:

\(m\) 个项目和 \(n\) 个员工。做项目 \(i\) 可以获得 \(A_i\) 元,但是必须雇佣若干个指定的员工。雇佣员工 \(j\) 需要花费 \(B_j\) 元,且一旦雇佣,员工 \(j\) 可以参加多个项目的开发。问经过合理的项目取舍,最多能挣多少钱。
\(n,m \le 100\)

将员工和项目都看做点,项目向其需要雇佣的员工连边。

题目变为给定一张有向图 \(G=(V,E)\),每个点有点权,且不存在负权点连向正权点的边。现在要你选择一个点集 \(V'\),满足

\[\forall e=(x,y) \in E,\ x \in V' \to y \in V' \]

考虑如下的网络流建图模型:
源点向所有正权点 \(P_i\) 连容量为权值的边,所有负权点 \(Q_i\) 向汇点连容量为权值相反数的边。
对于原图中的边 \(e=(x,y)\),从 \(x\)\(y\) 连一条容量为 \(\infty\) 的边。答案为正权总权值减去最小割。

从割的意义来理解,得到最大的收益即舍弃最小的收益(割)。
对于一个项目与一个员工,在网络流图中存在一条 \(S \to P_i \to Q_j \to T\) 的路径,
由于 \(P_i \to Q_j\) 容量为 \(\infty\),所以它不可能被割。
要么 \(S \to P_i\) 被割,表示舍弃这个项目;要么 \(Q_j \to T\) 被割,表示花钱雇佣他,
因此答案就是正权总权值减去最小的损失(即最小割)。

需要注意的是若图中有负权点连向正权点的情况,则此建图模型不适用。

Hall 定理

内容

设一张二分图两部点数为 \((x,y), x < y\),则其的一个完备匹配定义为左部 \(x\) 个点成为匹配点。

特别地,当 \(x = y\) 时,这类匹配也被称为完备匹配。

一个如上定义的二分图存在完备匹配充要条件是对于左部点的大小为 \(k\)任意子集 \(S\),这些点在右部连到的点集(也被称为 \(S\) 的邻域,记为 \(N(S)\))大小不小于 \(k\)。也就是说,只要保证对于 \(X\) 的任何子集,\(Y\) 中都有足够多的顶点可以与它匹配,就一定存在 \(X\)‑完美匹配.

证明

必要性

反证法:显然如果存在子集 \(S\) 满足 \(|N(S)|\) 小于 \(k\),这个子集必然无法完备匹配。

充分性

考虑“恰好卡死”的情况,即存在子集 \(S\) 满足 \(|N(S)|\) 等于 \(k\)。如果“恰好卡死”成立,充足匹配情况肯定也成立。(以下由 ai 生成)

存在一个非空真子集 \(A \subsetneq X\),使得 \(|N(A)| = |A|\)(这 \(|A|\) 个男生恰好只认识 \(|A|\) 个女生,一点余地都没有)。

关键想法:把问题切成两半分别解决。

第一半\(A\) 中的男生只能在 \(N(A)\) 中找对象。\(A\) 自身显然满足Hall条件(继承自原问题),且 \(|A| < n\),由归纳假设,\(A\) 中的男生可以与 \(N(A)\) 完美配对。

第二半:剩下的男生 \(X \setminus A\) 必须从剩下的女生 \(Y \setminus N(A)\) 中找对象。我们要验证这个小问题也满足Hall条件。

任取 \(S \subseteq X \setminus A\)。考虑男生集合 \(A \cup S\),它在原问题中满足Hall条件:

\[|N(A \cup S)| \geq |A \cup S| = |A| + |S| \]

\(N(A \cup S) = N(A) \cup N(S)\),所以 \(N(S)\)\(N(A)\)的部分至少有

\[|N(A \cup S)| - |N(A)| \geq (|A|+|S|) - |A| = |S| \]

也就是说,\(S\) 在剩下的女生中至少有 \(|S|\) 个候选人。第二半也满足Hall条件,且男生人数 \(< n\),由归纳假设可完成配对。

合起来:两半的配对互不冲突(用的女生不重叠),拼起来就是 \(n\) 个男生的完美配对。

题目

CF1519F Chests and Keys

参考 这个题解

首先,形式化描述,我们需要以下条件成立:设 \(L_x\) 为第 \(x\) 个宝箱上所有的锁的集合,则对于每个宝箱子集 \(S\),都要满足

\[\sum_{i\in S}a_i\le \sum_{j\in (\bigcup_{i\in S}L_i )}b_j \]

这个式子的形式长得很像 Hall 定理,类似于每个 \(a_i\) 的权值拆开成若干个 1,必须要和 \(b_i\) 的权值拆开成若干个 1 全部匹配上(因为 \(a_i\)\(b_i\) 很小所以可以这样做),所以考虑将原问题转化为二分图匹配的问题。

具体来说,对于每个宝箱 \(i\),将这个宝箱拆成 \(a_i\) 个点,同时,对于每个锁,将这个锁拆成 \(b_i\) 个点,如果宝箱 \(i\) 上有锁 \(j\),则将宝箱 \(i\) 拆出的所有点连一条边到锁 \(j\) 拆出的所有点,得到一个二分图,其中宝箱拆成的点在二分图的左部,则要求这个图的左部存在完美匹配(即左部每个点都能和右部的一个点匹配,且匹配点互不相同),下面只需要构造出这个完美匹配即可。

考虑 dp:设 \(f(i,mask)\) 表示前 \(i\) 个宝箱拆成的点都匹配完了,右部每个锁拆成的点中还没被匹配的个数(按照五进制压缩成 \(mask\)),最少需要花费的钱是多少。转移的时候,直接枚举当前宝箱对应的每一个点都匹配上了哪个锁拆成的点,如果匹配上了至少一个锁 \(j\) 拆成的点,则花费的钱需要加上 \(c_{i,j}\) 。最后,取 \(f(n, *)\) 的最小值即为答案。

时间复杂度 \(\mathcal O(n⋅5^{2n})\),常数非常小,可以通过。

题目

P2756 飞行员配对方案问题

网络流 24 题。最大匹配板子。

P1251 餐巾计划问题

网络流 24 题。最小费用最大流问题。首先我们很显然的思路就是把餐巾当成流量。考虑每天拆点拆成干净点与脏点,因为如果不拆点的话可能出现拿了脏毛巾就当干净毛巾送走了。(具体先看看下面)

对于每天的干净点,我们连一条边到汇点,流量为 \(r_i\),花费为 \(0\),表示干净毛巾被拿走用了。因为最小费用最大流算法会优先保证最大流,所以一定会流满。

每天再从源点连一条流量为 \(r_i\),花费为 \(0\) 的边到脏点,代表今天多了这么多脏毛巾。

剩下的就是两种清洗方案了,我们直接将这天的出点连向若干天后的入点(因为连过去就变成干净毛巾了)。还有购买新毛巾,我们直接从源点连向入点,加费用即可。

注意每天的入点要连向下一天的入点,因为干净毛巾可以留到下一天再用。

在这题中把 \(S\) 源点看做脏毛巾供给侧,\(T\) 汇点看做干净毛巾需求侧。最大流的设计保证了方案的合法性。

P2764 最小路径覆盖问题

网络流 24 题。路径的性质是,每个点的入度为 \(1\),出度也为 \(1\)。于是考虑把每个点 \(P\) 拆成入点 \(Px\) 和出点 \(Py\)

这样就变成一个二分图了。\(Px\) 与源点连,\(Py\) 与汇点连,如果有边 \(u \to v\)\(ux\)\(vy\)。边的容量都为 \(1\)

如果从源点与汇点有一条路径,则路中间两头的点(一出一入)可以连起来,最大流就是合并次数。一开始都是单个的散点(散点各成一条路径),每次合并就少一个路径,所以路径数量为点数 \(-\) 最大流。

找方案时可以把两个点看成同一个点,除去与源点、汇点的边,剩下流满的边组成的就是一种方案。

P1646 [国家集训队] happiness

最小割。

一个人要么选文,要么选理,最小割就是把网络分成和源点通、和汇点通两部分。我们考虑把选择文科的人割到源点,选择理科的割到汇点。那么直接源点向每个人连选文科的快乐值,每个人往汇点连理科的快乐值。

对于相邻的俩人,我们建两个超级点,源点向文科超级点连一条两人选择文科获得的快乐值,理科超级点向汇点连一条两人选理科的快乐值。然后文科超级点向两个人都连限流 \(+\infty\) 一条边,两个人向理科超级点连限流 \(+\infty\) 一条边(就是不许你割掉他)。前后左右都是这样处理。

然后跑一遍最大流,因为最大流等于最小割,答案就是图上剩余边的边权和,即 \(tot−flow\)\(tot\) 为所有的快乐值之和。

给超级点设置“不许割掉的边”,就是如果不割掉超级点与源点/汇点的边,就一定会导致流流到它所对应的相邻点对,这时候再割掉相邻点对与源点/汇点的相应边就不划算了(如果割掉这个边,流还是能流到这个点,那就和没割效果一样了)。这样就保证了最后方案的合法性。

P2754 [CTSC1999] 家园 / 星际转移问题

\(n,m,k\) 都很小,因此可以每一天都新建 \(n+1\) 个点表示星球,直接和下一天的星球按要求连边即可,连到月球的直接连到汇点。满足要求的天数 \(t\) 即建了前 \(t\) 天的星球和边就可以使汇点流量为 \(k\)。枚举天数,每次在上一天的基础上往里加新点和新边,跑 dinic 即可。二分答案的话,图好像不太好更新。 注意每次跑完 dinic 得到的最大流要和上一次跑完的累计起来,因为上次留下来的图是残量网络。

P2766 最长不下降子序列问题

最长不下降子序列的长度的常规解法是 dp。但是这里还要求求出“最多可取出”的数量。

第一问直接 dp。第二问,考虑建图。考虑 lis 最朴素的解法是记录每一位 \(i\) 作为 lis 结尾的最长长度 \(f_i\),复杂度是 \(O(n^2)\)。为了让取出的长度等于 lis 长度,在取的时候一定满足 \(f_i=f_j+1\),其中 \(i\)\(j\) 在取出串中的位置相邻。拆点,对于上面的每一组 \((i,j)\),让 \(i\) 的出点向 \(j\) 的入点连一条流量限制为 \(1\) 的边。如果不拆点就会出现如下错误情况:

这里点 3 被错误地覆盖了两次。

第三问建图差不多,只要把源点连向 1 入点,1 入点连向 1 出点,n 入点连向 n 出点,n 出点连向汇点的流量限制改为 \(+\infty\) 即可。

ZROJ #3495. 循环

模拟赛的题,倒闭了。

\(k=1\) 的时候,做法是拆点后的二分图,如果 \((u,v)\) 有边,则 \(u_左 \to v_右\) 连边。最大匹配为 \(n\) 的时候(完美匹配)有合法方案。

\(k=2\) 的时候,同样考虑二分图,只不过网络流连边的时候,源汇点相连的边的流量限制都为 \(2\)。输出方案的时候奇偶染色即可。(把 \(2n\) 条答案边看做无向边做深搜,比如某条边向右,那深搜的下一条边往左,往左的边和往右的边在实际方案里是共用一个终点的,所以染成不同颜色。)

posted @ 2026-05-19 11:00  Jenny_yu  阅读(8)  评论(0)    收藏  举报