二分图相关
写了好多好多,结果学校电脑过热蓝屏了,没保存,我阐释你的梦。
0.1. 增广路
0.1.1. 定义
-
交错路(alternating path)始于非匹配点且由匹配边与非匹配边交错而成。
-
增广路(augmenting path)是始于非匹配点且终于非匹配点的交错路。增广路中边的数量是奇数。形如 (
)。
增广路上非匹配边比匹配边数量多
有交错路不一定有增广路。
0.1.2. 增广路定理 Berge's lemma
匹配
考虑转证如下:
中有增广路时,不是最大匹配。
proof. 设
不是最大匹配时,存在增广路。
proof. 设
这样我们就证明了增广路定理。
0.2. 二分图最大匹配
0.2.1. 增广路算法
有了上面的定理,很容易想到二分图匹配的一个算法流程:
不断寻找增广路,找到则增广一次,将答案加一,否则退出。
简单实现是
假设交错路的出发点
用一个队列维护 bfs(进入过队列的点都属于
-
初始时将未匹配的左部点
加入队列。 -
遍历队列中点的出边,找到右部点
,如果 存在另一组匹配 ,则将 加入队列。
为了不重复入队扩展,考虑记录
实现起来很简单。
int xlink[N],ylink[N],pre[N]; int find(){ queue<int> q; for(int i=1;i<=m;i++) pre[i]=0; for(int i=1;i<=n;i++) if(!xlink[i]) q.push(i); while(!q.empty()){ int x=q.front(); q.pop(); for(auto y:g[x]){ if(pre[y]) continue; pre[y]=x; if(ylink[y]) q.push(ylink[y]); } } for(int i=1;i<=m;i++) if(!ylink[i]&&pre[i]) return i; return 0; } void getans(){ while(1){ int p=find(); if(!p) return; ans++; for(int x=pre[p];x;x=pre[p]){ int t=xlink[x]; ylink[p]=x,xlink[x]=p,p=t; } } }
0.2.2. Hopcroft-Karp 算法
本质上就是 Dinic 算法求解二分图匹配。
将增广路算法里的每次只找一条增广路改进为每轮找最短的不相交的极大增广路集合(maximal set of disjoint shortest Aug path)。
注意是极大而不是最大,因为可能无法找到最大的集合,只能退而求其次找一个无法再增加的集合。
该算法的时间复杂度为
记路径
- Lemma 0.2.2.1. 每一轮寻找到的增广路长度严格大于上一次(
)。
proof. 分讨
-
与 没有重合的点。此时若 ,则不符合极大,同样的,若 ,则不符合最短,故不相交时 。 -
与 有重合的点,可以证明此时一定有至少一条边重合,所以有 。又因为 中有 条顶点不相交的增广路,故 ,否则一定可以找到更短的增广路在前面更新。
所以有
假定最坏情况下该算法在第
- Lemma 0.2.2.2.
。
proof. 由 Lemma 0.2.2.1. 可知,第
根据流分解定理,因为
而
每条不相交的增广路长度的最小值为
实现起来先 bfs 再 dfs,本质上就是 Dinic 算法。
于是同理我们称 Dinic 算法在求解二分图最大匹配时,复杂度为
int xlink[N],ylink[N],vis[N],dx[N],dy[N]; int mx; bool bfs(){ mx=inf; queue<int> q; for(int i=1;i<=n;i++) dx[i]=inf; for(int i=1;i<=m;i++) dy[i]=inf; for(int i=1;i<=n;i++){ if(!xlink[i]){ dx[i]=0; q.push(i); } } while(!q.empty()){ int x=q.front(); q.pop(); if(dx[x]>mx) break; for(auto y:g[x]){ if(dy[y]!=inf) continue; dy[y]=dx[x]+1; if(!ylink[y]) mx=dy[y]; else dx[ylink[y]]=dy[y]+1,q.push(ylink[y]); } } return mx!=inf; } bool find(int x){ for(auto y:g[x]){ if(vis[y]||dy[y]!=dx[x]+1) continue; vis[y]=1; if(ylink[y]&&dy[y]==mx) continue; if(!ylink[y]||find(ylink[y])){ xlink[x]=y,ylink[y]=x; return 1; } } return 0; } int work(){ int ans=0; for(int i=1;i<=n;i++) xlink[i]=0; for(int i=1;i<=m;i++) ylink[i]=0; while(bfs()){ for(int i=1;i<=n;i++) vis[i]=0; for(int i=1;i<=n;i++) if(!xlink[i]) ans+=find(i); } return ans; }
0.3. Hall 定理
0.3.1. 内容
设二分图
0.3.2. 证明
该定理的必要性很显然,因为若存在
对于充分性,则可以从逆否命题的角度进行证明。
(说句闲话,否命题和命题的否定不是一个东西,高中数学考的一般是命题的否定;否命题和逆命题是等价命题,原命题和逆否命题是等价命题)
原命题:若
命题的否定:若
否命题:若
逆否命题:若
扯远了,现在需要转证其逆否命题。
(upd:勘误,详见 link,逆否命题是对的)。
考虑设现在图的最大匹配
- Lemma 0.3.2.1.
和 中没有连边。
proof. 反证,假设存在一条边,分情况讨论该条边
-
,不合法,因为会从 扩展到 ,故 一定在 中。 -
,同样不合法,因为此时 的前驱是 ,故 一定在 中。
- Lemma 0.3.2.2.
。
proof. 因为现在图的最大匹配
现在我们取
Q.E.D.
0.3.3. 引理
若二分图
- 证明
隐含的信息是
若左边选了集合
- 应用
proof. 只考虑能不能填上第
0.4. Kőnig 定理
0.4.1. 相关概念
不写了,见 oi-wiki。
对于二分图
proof.
故最大独立集
0.4.2. 内容及证明
二分图
从
这个比较显然,因为最大匹配的边一定是独立的,必须选出至少
proof. 只需要证明存在一组大小为
仍然考虑 0.3.2. 提到的
由于
容易知道
现在考虑证明
前者,因为这是最大匹配,所以不能存在增广路,也就是
也就是说我们可以选择
这样我们就证明了 Kőnig 定理。
0.4.3. 其他相关
- DAG 最小路径覆盖 (1)
定义为用最少数量的路径覆盖所有的点,满足路径没有顶点重合(vertex disjoint),路径长度可以为
DAG 最小路径覆盖
这个二分图构造为,对于原 DAG
匹配一次就相当于把两条路径合并,可以理解为一条路径会被终点统计到,因为终点没有匹配。
- DAG 最小路径覆盖 (2)
路径和链的覆盖问题都是看语境的。
这里定义为用最少数量的路径覆盖所有的点,点可以重合。
考虑建立二分图
接下来就和 (1) 问题一样了,因为如果用到了一条可达的路径,就相当于强制钦定了不经过中间的点。
可达性可以用 bitset 优化传递闭包。
- DAG 最小路径覆盖 (3)
是 0.6.1. 的应用。
定义为用最少数量的路径覆盖所有的边,边都可以重合。
定义偏序关系
容易发现该偏序关系具有三个性质,所以我们将问题转换成偏序集的最小链覆盖问题,故又变成了 (2) 问题。
若不是 DAG,则可以采用上下界网络流进行求解,在此不做赘述。
0.5. UVG 游戏
Undirected Vertex Geography Game
无向图地理游戏,指在一个无向图中,只有一个起点,上面有一个棋子,两个玩家轮流沿着边推动棋子,不能走重复的点,无法行动则失败。
0.5.1. 结论
如果无向图
0.5.2. 证明
仍然从两个方面来证明充要,采用反证法。
- 无向图
的所有最大匹配都经过起点,则先手必胜。
proof. 先手策略为 move-along
假设先手失败,则当前仅当走到了一个非匹配点,此时没有另外的出边了,但是注意到我们可以将走过的路径反转,这样仍然是最大匹配,且不再经过起点,所以假设不成立,先手必胜。
存在一组最大匹配使得该匹配不经过起点,则先手必败。
proof. 先手随便选一个点走,此时后手策略为 move-along
0.6. Dilworth 定理
0.6.1. 相关概念
- 偏序集
若集合
- 链与反链
对偏序集
对偏序集 S 和其上的偏序
0.6.2. 内容及证明
对有限偏序集
注意 Dilworth 定理定义在偏序集中,或者说是一个已经传递闭包后的 DAG,那么这里链覆盖能不能经过重复点是一样的。
如果在普通 DAG 中,并不去求它的传递闭包,不可比定义为不可达,那么这里链覆盖实际上就是点可以相交的路径覆盖。
上者引自 dwt 的博客。
设
类比 Kőnig 定理,我们从
proof. 显然,因为若存在反链
故反链大小不能超过最小的链覆盖数量。
若存在
则现需转证存在一组构造使得
proof. 对于一张偏序集构成的 DAG,对它构造二分图
于是有
现给出构造:若点
考虑从两方面证明该构造的正确性:
为一个反链。
proof. 采用反证法,若
。
我们容易发现只有
而前面已经证明过
于是我们证明了 Dilworth 定理,同时得到了一个最大反链的构造方法——求出最小点覆盖,选择两部点都不在最小点覆盖中的点。
所以还可以得到偏序集二分图的性质,即为不存在
这一点其实也可以予以证明:
假设
0.7. 二分图最佳匹配
0.7.1. 概念
- 二分图最大权完美匹配
二分图中边权和最大的完美匹配。
我们认为,在这张二分图中,任意一个左部点都有和所有右部点的连边,左右点数相等,且边权均为非负整数,称其为标准情况,对于任意一张二分图,可以通过调整来达到该限制。
具体地,若边数不够,则添加边权为
若左右点数不相等,则将小的一边补齐,划归到非负情况下添加
于是使用 KM 算法(匈牙利算法)就可以解决该问题。
- 二分图带权最大匹配
二分图中边权和最大的最大匹配。可以通过加入边权为
- 二分图最大权匹配
二分图中边权和最大的匹配。可以通过加入边权为
0.7.2. KM 算法(匈牙利算法)
其实匈牙利算法是解决最佳匹配而不是最大匹配的说...
以
引入一些概念:
-
可行顶标:给每个节点
分配一个权值 ,称该权值为 的顶标,对于所有边 满足 ,则称 是一组可行顶标。 -
相等子图:在一组可行顶标下原图的生成子图,包含所有点但只包含满足
的边 。
- Lemma 0.7.2.1. 对于某组可行顶标,如果其相等子图存在完美匹配
,那么, 就是原二分图的最大权完美匹配。
proof. 对于该完美匹配
设原二分图的最大权完美匹配为
所以
于是我们有了一个简单的想法:初始随即设定一组可行顶标,通过不断的调整使得相等子图为完美匹配。
考虑对于初始的相等子图跑最大匹配,可以得到下图的情况:
沿用 0.3.2. 中提到的
考虑调整可行顶标,我们给
-
与 中连边不变。 -
与 中连边不变。 -
与 中连边可能减少。这个其实没什么影响,因为去掉的边一定都是非匹配边,且两端点均为匹配点。 -
与 中可能产生连边。
考虑
当一条新的边
-
是未匹配点,则找到增广路,重新求 和 。 -
和 中的点已经匹配,则顺着一直走下去,修改 和 的部分值。
由于每次修改至少使
于是我们得到了一个算法流程:
-
随便选择一组可行顶标,求出
、 和 。 -
当
时执行如下操作直到 (while):- 求出
。 - 修改可行顶标权值,通过一条边的增加来计算
是否会扩大。 - 对应修改
和 。
- 求出
试分析复杂度。
首先找到一次增广路需要
考虑内部的三个操作,操作
考虑优化该操作使得复杂度变成
记
考虑三种操作的复杂度:
-
在求
时,遍历每一个 中的 ,求 的最小值。 -
修改可行顶标时,
。 -
扩展增加
时,修改 ,可能变得更小,该操作均摊只会进行 次。
单次操作复杂度为
实际实现的时候并不需要先求出最大匹配再找到
bool check(int y){ sy[y]=1; if(ylink[y]){ q.push(ylink[y]); sx[ylink[y]]=1; return 0; } while(y){ ylink[y]=pre[y]; swap(y,xlink[pre[y]]); } return 1; } void bfs(int st){ q.push(st); sx[st]=1; while(1){ while(!q.empty()){ int x=q.front(); q.pop(); for(int y=1;y<=n;y++){ if(sy[y]) continue; ll t=lx[x]+ly[y]-w[x][y]; if(slack[y]>=t){ pre[y]=x; if(t) slack[y]=t; else if(check(y)) return; } } } ll a=inf; for(int y=1;y<=n;y++) if(!sy[y]) a=min(a,slack[y]); for(int i=1;i<=n;i++){ if(sx[i]) lx[i]-=a; if(sy[i]) ly[i]+=a; else slack[i]-=a; } for(int y=1;y<=n;y++) if(!sy[y]&&!slack[y]&&check(y)) return; } }
其实看代码分析复杂度更清晰了,由于是从每一个点出发寻找增广路,所以 while(1)
操作至多进行
KM 算法在找到最大权完美匹配的同时,也找到了一组权值和最小的可行顶标,所以若要求解最小可行顶标,可以使用 KM 算法。
注意如果左右点数不相等只能求解最小非负可行顶标。
本文作者:syta
本文链接:https://www.cnblogs.com/syta/p/18100861
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步