浅谈二分图
zhang_kevin · · 算法·理论
本文同步发表于「博客园」。
链接:https://www.cnblogs.com/zhang-kevin/articles/18931608。
正文开始~
前言
-
这篇文章改编自我曾经做的二分图课件,所以文字可能比较奇怪,请谅解;
-
文章中提供一些例题,建议先独立思考再看题解(题解不提供代码);
-
给出题单:https://www.luogu.com.cn/training/711260。
\texttt{Part 1} :二分图基本知识
我们先学习一些二分图相关的基本知识。
二分图的定义
二分图,又称二部图,他的节点由两个集合组成,且两个集合内部没有边。
换句话说,一个图是二分图,要求存在一种方案,可以将节点划分成满足以上性质的两个集合。
例如,这是二分图:
二分图的性质
- 如果两个集合中的点分别染成红色和蓝色,可以发现二分图中的每一条边都一定是连接一个红色点和一个蓝色点。
- 二分图不存在长度为奇数的环(因为每次都是从一个集合走到另一个集合,所以必须走偶数次才能回到出发的集合)。
二分图的判定
这里有一个很重要的判定定理:
一个图是二分图当且仅当它不包含奇数长度的环
在使用代码判断的时候,我们考虑染色法。
对图进行一次 dfs,第一个点染成红色,与之相邻的染成蓝色。
然后对于蓝色点相邻的所有点染成红色。
重复上述过程。
如果发现一个点即将要染的颜色与原来的颜色不同,说明这个图不是二分图。
反之这个图是二分图,染色结果就是构造。
例题 \texttt{1}
有
特别的,你不知道什么是并查集。
题解
这就是经典的关押罪犯,我们考虑不使用并查集来解决。
显然可以二分答案。具体来说,将所有罪犯的冲突 mid 表示冲突数组中的第几个(可以理解为二分下标)。
我们将比 check 函数判断该图是否是二分图即可,判断方法就是染色法。
\texttt{Part 2} :二分图最大匹配相关知识
下面我们学习二分图最大匹配相关知识。
前言
这是一个在 OI 界中几乎不会以单质(所以是化合物?)存在的东西。
但二分图最大匹配常结合一些复杂的建模,以及一些其他算法。
所以,还是有必要学习的。
另外很多建模用的 trick 也需要通过刷题得到。
二分图最大匹配定义
匹配的定义
-
定义:图
G 的一个匹配是G 的一个边集E ,满足E 中的所有边都没有公共端点。 -
说人话就是一张图的一个匹配是一堆没有公共端点的边。
最大匹配的定义
- 显然就是边数最多的匹配。
注意这不是二分图最大权匹配。
匈牙利算法
匈牙利算法是常用的二分图匹配算法,其正确性基于 Hall 定理。
算法的时间复杂度为
我们先通过图示简单理解一下算法过程(图完全手绘,不好看请谅解……)。
下图是一个普通的二分图。
我们称呼左边的点为红
我们先取第一条边(
现在红
这个时候,我们发现,蓝
于是,我们让蓝
显然,他的原配红
现在考虑红
然后,蓝
于是,我们再次让蓝
他的原配红
最后考虑红
现在,蓝
于是,我们再次让蓝
但他的原配红
于是红
又因为他没有第二条边了,所以算法结束。
最终得到结论:最大匹配等于
算法过程的文字描述:枚举每一个左部点
参考代码:
bool dfs(int u){
for(int i = head[u], v; i; i = nxt[i]){
if(!vis[v=to[i]]){
vis[v] = 1;
if(!match[v] || dfs(match[v])){
match[v] = u; return true;
}
}
}
return false;
}
void solve(){
for(int i = 1; i <= leftn; i++){
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
}
特别的,这个算法需要以增广路的思想去理解,因为后面的学习需要用到。
网络流求二分图最大匹配
这里不详细介绍网络流了,算法过程就是建立一个源点和一个汇点,然后跑一遍 Dinic。
Hall 定理
下面讲一下 Hall 定理。
定理内容:
对于一个二分图
G ,假设G 的两个集合分别为X 、Y ,且X 小一些,有n 个点;则G 存在完美匹配的充分必要条件是对于任意一个X 的子集,设大小为k ,那么和这个子集相连的Y 必须不小于k 个。
必要性证明:
假设一个二分图
那么一定存在
那么,请问你们都是怎么被匹配上的?
因此必要性正确。
充分性证明:
假设一个二分图
设其最大匹配为
则一定可以从某个点
因此充分性正确。
推论
Hall 定理推论:
二分图
G 的最大匹配为|X| - \max (|X'|-|N(X')|) ,其中X 是数量较少的集合,X' 是X 的任意子集,N(u) 表示u 的邻居节点集合。
例题 \texttt{2}
一共有
本题存在 Special Judge。
对于
题解
题目:https://www.luogu.com.cn/problem/P2756。
显然建二分图。
如果你使用匈牙利算法,那么恭喜你,你的算法自带匹配方案。
如果你使用网络流,只需要在求出答案后,判断边是否有流量即可。
例题 \texttt{3}
给出一张
题解
考虑黑白交替染色,这样每一块骨牌需要覆盖一黑一白。
白色当成左部点,黑色是右部点,相邻则连边。
显然求出最大匹配就好了。
例题 \texttt{4}
给定一个
问棋盘上最多能放多少个不能互相攻击的車。
車放在格子里,攻击范围与中国象棋的“車” 一致。
数据范围:
题解
题目:https://www.luogu.com.cn/problem/P10937。
考虑每一行当成左部点,每一列当成右部点,如果行列相交就连边。
这样一个边被选中,代表放置一个車,而且要求其他边与之没交点。
显然这就是最大匹配,于是跑一遍匈牙利或网络流就好了。
例题 \texttt{5}
城堡有
数据范围:
题解
题目:https://www.luogu.com.cn/problem/P10936。
显然答案具有单调性,所以考虑二分答案。题目变为
考虑建二分图,左边是防御塔,右边是敌人。
显然我们可以计算出
因此我们把一个防御塔拆成
最后判断最大匹配是不是敌人个数就好了。
特别的,二分图中的点可以被匹配多次(但仍有上限)前提下的最大匹配问题,称为二分图的多重匹配问题。拆点是经典的解决方法,网络流也不错。
补充知识
补充知识:二分图最小点覆盖、二分图最大独立集。
二分图最小点覆盖
最小点覆盖:选最少的点,满足每条边至少有一个端点被选。
König 定理:二分图中,最小点覆盖包含的点数等于最大匹配包含的边数。
证明如下:
显然二分图最大匹配是原图的一个子集,而且所有边不相交。
所以,选择最小点覆盖的时候至少要每条边选一个。
也就是:最小点覆盖包含的点数大于等于最大匹配包含的边数。
所以,我们只需要给出一种构造,使得点覆盖数量等于最大匹配包含的边数即可。
考虑如下构造:
从左侧未匹配的节点出发,按照匈牙利算法中增广路的方式走,即先走一条未匹配边,再走一条匹配边。
由于已经求出了最大匹配,所以这样的增广路一定是不完整的(以匹配边结束)。
在所有经过这样「增广路」的节点上打标记。则最后构造的集合是:所有左侧未打标记的节点和所有右侧打了标记的节点。
下证这个集合的大小等于最大匹配以及这个集合是一个点覆盖。
首先,这个集合的大小等于最大匹配。考虑这些事情:
-
左部非匹配点一定被标记:因为你们是出发点;
-
右部非匹配点一定没有被标记:否则你的匈牙利就出 Bug 了(有增广路);
-
每对匹配点要么都被标记,要么都没被标记:因为左部匹配点只能通过右部到达。
我们的选择是:所有左侧未打标记的节点和所有右侧打了标记的节点。
因此,一定正好对每条匹配边选择了一个点。
故选出的点数正好等于二分图的最大匹配数。
二分图最大独立集
最大独立集:选最多的点,满足两两之间没有边相连。
因为在最小点覆盖中,任意一条边都被至少选了一个顶点,所以对于其点集的补集,任意一条边都被至多选了一个顶点,所以不存在边连接两个点集中的点,且该点集最大。因此二分图中,最大独立集
例题 \texttt{6}
在一个
对于给定的
如图所示,西洋棋的“长脖子鹿”,类似于中国象棋的马,但按照“目” 字攻击,且没有中国象棋“别马腿” 的规则。(因为长脖子鹿没有马腿)
给定一个
在一块
保证:
题解
题目:https://www.luogu.com.cn/problem/P6062。
以样例为例:
黄色是泥地,绿色是草地。
考虑这两块木板:
显然左边更优。
于是,枚举所有木板可能的放置位置:
一个格子要么被横着的木板覆盖,要么被竖着的格子覆盖。
所以写出每个格子被覆盖需要选择哪两块木板。
我们把横着的看做左部点,竖着的看做右部点。
如果两块板子同时满足一个格子的需求,就把这两个板子连边。
显然,选择一个点就是放置一块木板,而 与这个点所连的所有边 所对应的格子 都可以被这块木板覆盖。
于是我们求一下最小点覆盖就可以了。
由 König 定理得最小点覆盖等于最大匹配。
所以跑一遍匈牙利或者网络流就解决问题了。
\texttt{Part 3} :二分图最大权匹配相关知识
二分图最大权匹配指的是:对于一个带权值的二分图,需要找一些匹配边,使得它们权值之和最大。
对于一个二分图,我们可以补充一些节点使得二分图两边点数相同,再连一些权值为
此时,题目转化我求二分图最大权完美匹配,可以使用 KM 算法 解决。
KM 算法
KM 算法是解决二分图最大权完美匹配的非费用流算法。
他可以在
先说一些定义。
定义
顶标:点的一个标记,满足
相等子图:原图的一个子图,满足子图中任意边满足
交错树:增广路径形成的树。
定理:
对于某组可行顶标,如果其相等子图存在完美匹配,那么,该匹配就是原二分图的最大权完美匹配。
证明:
令
一个相等子图的完美匹配
所以,任意一组完美匹配的边权和都不会大于
于是,算法流程是这样的:确定顶标 -> 尝试匹配 -> 如果有完美匹配则结束 -> 否则修改顶标 -> 再次尝试匹配 ->・・・・・・
初始顶标不妨设
调整顶标的目的就是要不断往相等子图内加入新的边。
假设现在需要调整顶标,则说明尝试匹配时匈牙利失败了。
对于匹配失败的那个左部点,他 dfs 时形成了一颗交错树。
容易发现以下结论:
交错树中所有右部点都是左部点通过非匹配边访问到的。
我们考虑如何让一个左部点可以访问到更多的右部点。
我们把交错树中所有左部点顶标
记交错树为
对于一条匹配边,显然要么
因为左部点是被右部点递归访问的,所以只需要考虑
-
若
i,j \in T ,则顶标和不变,以前能访问到的现在还能访问; -
若
i \in T,j \notin T ,则顶标和变小,之前访问不到的点,现在有可能访问了。
为了保证顶标满足定义,所以我们找到最小的
只要原图存在完美匹配,这样的边一定存在。
上述做法不仅不会破坏之前的结果,还可以使交错树中至少一个左部点可以访问更多的右部点。
不断重复以上过程,直到所有左部点都找到匹配,就解决了问题。
具体实现时,我们可以维护一个数组记录可能更新的
时间复杂度
考虑到每次修改顶标后,相等子图只会扩大,所以没必要重新进行完整的 dfs。
我们记录一个
匹配成功后,由于 dfs 不一定从根开始,所以我们无法利用回溯更新增广路,需要记录
时间复杂度降到了
给出参考代码(可通过模板题 P6577):
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)){
x = (x<<1) + (x<<3) + (ch^48);
ch = getchar();
}
return x*f;
}
const int N = 1001, M = 1e5 + 1, inf = 1e18 + ~-1;
bool va[N], vb[N]; //是否访问
int n, m;
int la[N], lb[N], w[N][N], last[N], upd[N], match[N];
//la、lb 表示顶标
//w 表示边权
//upd[j] 表示表示每个右部点 j 对应的最小 delta
//last 表示每一个右部点在交错树中的上一个右部点
inline bool dfs(int u, int fa){
va[u] = true;
for(int v = 1; v <= n; v++){
if(!vb[v]){
if(la[u] + lb[v] - w[u][v] == 0){
vb[v] = true;
last[v] = fa;
if(!match[v] || dfs(match[v], v)){
match[v] = u;
return true;
}
}else if(upd[v] > la[u] + lb[v] - w[u][v]){
upd[v] = la[u] + lb[v] - w[u][v];
last[v] = fa;
}
}
}
return false;
}
inline int KM(){
for(int i = 1; i <= n; i++){
la[i] = -inf; lb[i] = 0;
for(int j = 1; j <= n; j++){
la[i] = max(la[i], w[i][j]);
}
}
for(int i = 1; i <= n; i++){
memset(va, 0, sizeof(va));
memset(vb, 0, sizeof(vb));
memset(last, 0, sizeof(last));
memset(upd, 0x7f, sizeof(upd));
// 从右部点 st 匹配的左部点开始 dfs,一开始假设有一条 0 -> i 的匹配
int st = 0; match[0] = i;
while(match[st]){
int delta = inf;
if(dfs(match[st], st)) break;
for(int j = 1; j <= n; j++){
if(!vb[j] && upd[j] < delta){
delta = upd[j];
st = j;// 下一次直接从最小边开始 dfs
}
}
for(int j = 1; j <= n; j++){ //修改顶标
if(va[j]) la[j] -= delta;
if(vb[j]) lb[j] += delta; else upd[j] -= delta;
}
vb[st] = true;
}
while(st){ // 倒推更新增广路
match[st] = match[last[st]];
st = last[st];
}
}
int ans = 0;
for(int i = 1; i <= n; i++) ans += w[match[i]][i];
return ans;
}
signed main(){
n = read(), m = read();
for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) w[i][j] = -inf;
for(int i = 1; i <= m; i++){
int u = read(), v = read(), W = read();
w[u][v] = W;
}
printf("%lld\n", KM());
for(int i = 1; i <= n; i++) printf("%lld ", match[i]);
return 0;
}
例题 \texttt{9}
有
小 D 有一个长度为
小 D 有
小 D 可以以任意顺序执行操作,且每种操作都可进行无限次。现在给定序列与所有操作,请你帮她判断是否存在一种方案能将 YES,否则输出 NO。
数据范围:
题解
题目:https://www.luogu.com.cn/problem/P6185。
考虑把每个位置当成一个点,把操作
操作
连边后的图分为很多联通块,对于一个联通块,有以下三种情况:
-
只有一个点,此时这个点如果不是目标值,就判无解。
-
联通块内存在边。显然「都加
1 或都减1 」的奇偶性不变。故判断一下「联通块内所有数的和 减去 联通块内所有数的目标值的和」的奇偶性即可,奇数判无解。 -
这是一个没有奇环的图,即二分图(包含于
2 情况)。显然「都加1 或都减1 」的差不变。所以「二分图两个集合的差」与「二分图两个集合目标值的差」不同时,判无解。
如果判断完所有联通块都没有返回无解,答案就是存在。
例题 \texttt{11}
给定平面上的
保证无三点共线,数据保证有解。
有
一个匹配即对这
一个匹配的幸福值即这
经典的问题是计算幸福值最大的匹配,即完美匹配。然而完美匹配有时候并不唯一,你需要计算对于所有的完美匹配,其交集是什么。
有
高桥君等人中,第
高桥君要增加椅子,使他们都能够坐在椅子上休息。 椅子可以添加到任意的实数坐标上,请求出需要添加椅子数量的最小值。
下面是一个经过部分改动的求
function Euclid(a, b):
if a < b:
swap(a, b)
if b == 0:
return a
r = reminder from dividing a by b (即设 r 为 a mod b)
if r > 0:
append r to the back of t (即将 r 插入到 t 的尾部)
return Euclid(b, r)
有一个由数对构成的序列
- 每个数对中的元素都是不超过
m 的正整数。 - 根据序列
p 可以经过上述操作得到输入中给定的t 。
有解输出任意一组解,无解输出 -1。
题解
题目:https://www.luogu.com.cn/problem/CF1684G。
注意到数对
为了方便,我们称大于
注意到:
Euclid(a, b) --> t1
Euclid(b, t1) --> t2
如果
因此一个数对不能生成两个大数。
另一方面,对于大数
if y|x:
2x+y mod x+y = x
x+y mod x = y
x mod y = 0 (0 isn't in t)
而且一个数对生成一个大数和一堆小数,可以转化为上述构造与一堆单独的小数。因为这样构造后函数的调用次数一定不超过
于是我们考虑构造如何匹配大数和小数。
我们把大数看做左部点,小数看做右部点,对于大数
然后求二分图最大匹配即可,我们的目标是每个大数都能被匹配(小数可以自己生成)。如果大数匹配失败可以直接判无解。
最后根据匹配的构造使用
匈牙利算法的最终复杂度是
网络流复杂度
结语
(学习我校大神的传统艺能)
以上,我们探讨了二分图这一图论的概念、性质以及它的应用场景。
从基础的判定,到求最大匹配,二分图在许多题目中扮演着不可或缺的角色。
随着技术的不断进步,二分图的应用也在不断扩展,从简单的黑白匹配,到复杂的二分图博弈,它都展现出了强大的生命力和灵活性。
我希望本文能够帮助读者更好地理解二分图,激发大家对二分图更深层次探索的兴趣。
在未来的学习和实践中,二分图无疑将成为解决各种问题的有力工具。让我们一起期待二分图在新技术中的应用,以及它如何帮助我们构建更加智能和高效的系统。
感谢您的阅读,如果您对二分图有更深入的问题或想要探讨相关话题,我们欢迎您的反馈和讨论。
改编自:2024 BNDSOJ 论文《用字典树实现平衡树》结语部分 ———— masonxiong。
参考资料
-
二分图最大权匹配 —— OI Wiki。
-
论文《The Dynamic Hungarian Algorithm for the Assignment Problem with Changing Costs》
-
《算法竞赛进阶指南》—— 李煜东 著。
-
2024 BNDSOJ 论文《用字典树实现平衡树》结语部分 —— masonxiong 著。