序
我觉得我现在还可以
正文
本文权当作学习二分图匹配的一些笔记,个人风格极为严重,请勿深究语文
匈牙利
本文不介绍算法的步骤,因为可以 google(逃,写的都是一些自己的总结
匈牙利的算法主要功能是可以做二分图的最大匹配。它是一种交替的找增广路的算法,其本质就是逆流修改(Singercoder 说的)
其核心的就是 re 这个数组不会回溯 (当然每次要清)
为什么呢,其实我觉得和当前当前弧优化有点联系。你想想,他要是回溯的话,都不是多项式算法了(笑)。但是其算法正确性也是有的。re 表示的是这个结点是否寻找过增广路。
理论上最坏时间复杂度会到 \(O(VE)\) 还行吧。一般卡不了。
算法伪代码
main()
{
last...
for 1 to n
fill re with 0
/* 每一次都要清空 re */
if find(i) ans++;
next...
}
find(u)
{
for any edge of u
v = e.v;
/* 与暴力算法的区别,和当前弧异曲同工吧 */
if re[v]
continue
/* 打上标记 */
re[v] = 1
if p[v] == NULL or find(p[v])
/* 逻辑要清晰 */
p[v] = u
return true
return false
}
写的不是很好,但是还算清楚吧。毕竟代码实现的能力也很重要的(误)。
然后通过这个操作,就可以求出二分图的最大匹配了。ヾ(≧∇≦*)ゝ
当然了,谈到二分图匹配自然会有 Singercoder 冒出来,你匈牙利是 \(O(nm)\) ,我最大流跑是 \(O(\sqrt n m)\) (没有严格证明,听dalao说的),客观的说,其实各有千秋吧。
- 运行时间:几乎相等,因为没有毒瘤可以把匈牙利卡到 \(O(nm)\),而且 ISAP 的常数肯定比匈牙利大呀.
- 代码实现难度:匈牙利完爆各种最大流
- 写完的成就感:最大流完爆匈牙利
- 运行空间:会卡这个?
真正的代码
bool find(int u) {
for (int i = h[u]; i != -1; i = edge[i].lac)
{
int to = edge[i].to;
if (re[to]) continue;
re[to] = 1;
if (!p[to] || find(p[to])) { p[to] = u; return 1; }
}
return 0;
}
然后我们就可以用它来搞各种与二分图有关的东西。
下文介绍几种别的扩展
- 最少点覆盖
- 最少边覆盖
- 最大独立子集
- 最少不相交路径覆盖
最小点覆盖
反证一下,他应该就是最大匹配数,因为如果有边并未覆盖。那么他的左右端点应该都没被选,所以就不是最大匹配了,矛盾
所以:最小点覆盖 = 最大匹配数
模板:poj1325(有坑)
最少边覆盖
这么思考,贪心的想,先把所有匹配的边选了,然后对于剩下的点随便选一个边
于是:最少边覆盖 = 点数 - 最大匹配数
模板:poj3020
最大独立子集
理解为一个删点和与其相连的所有边,那么剩下的点就是独立子集。删去最小点覆盖即可,
于是最大独立子集 = 点数 - 最小点覆盖
模板:poj1466(据说数据有坑但是没有掉进去...但是还是很坑
最少不相交路径覆盖
ps:我实现这个一般都是网络流..因为 luogu 的模板要输出路径...
这么想,我们最开始有 n 个不相交路径(n 个点),然后要进行连边,连尽可能多的边,就可以减少路径数...
然后,把一个点分为前点后点做二分图匹配即可。
最少路径数 = 点数 - 最大匹配数
模板:poj1422
KM算法
KM算法是可以解决完备匹配下的最大权匹配,个人认为是对匈牙利进行了一番操作)
完备匹配
完备匹配就是要求匹配数为 n
书面的说:所谓的完备匹配就是在二部图中,x 点集中的所有点都有对应的匹配且 y 点集中所有的点都有对应的匹配,则称该匹配为完备匹配。
浅析算法正确性
该算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转化为求完备匹配的问题的。设顶点 \(xi\) 的顶标为 \(lx[i]\),顶点 \(y_j\) 的顶标为 \(ly[j]\) ,边权为 \(mp[i][j]\) 。在算法执行过程中的任一时刻,对于任一条Edge \(<i,j>\) 有 \(lx[i] + ly[j] \ge mp[i][j]\) 始终成立。
给出一条定理:若由二分图中所有满足\(lx[i] + ly[j] = mp[i][j]\) 的边 \(<i,j>\) 构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。
这是显然的,因为对于每个匹配他其他的选择的边权一定满足 \(mp[i][j] \le lx[i] + ly[j]\) 要保证其有最大值,只能选相等子图的
其最佳答案即为 \(\sum lx[i] +ly[i]\) 所以
初始时为了使 \(lx[i] + ly[j] \ge mp[i][j]\) 恒成立,令 \(lx[i]\) 为所有与顶点 \(x_i\) 关联的边的最大权,\(ly[j] = 0\)。如果当前的相等子图没有完备匹配,就按下面的方法修改顶标以使扩大相等子图,直到相等子图具有完备匹配为止。
我们求当前相等子图的完备匹配失败了,是因为对于某个 \(x\) 顶点,我们找不到一条从它出发的交错路。这时我们获得了一棵交错树,它的叶子结点全部是 \(x\) 顶点。我们把交错树中 \(x\) 顶点的顶标全都减小某个值 \(d\), \(y\) 顶点的顶标全都增加同一个值 \(d\) ,那么我们会发现:
1)两端都在交错树中的边 \(<i,j>\) \(lx + ly\)(以下简称xy)的值没有变化。也就是说,它原来属于相等子图,仍属于相等子图。
2)两端都不在交错树中的边 \(<i, j>\) ,\(xy\) 都没有变化。也就是说,它原来属于(或不属于)相等子图,仍属于(或不属于)相等子图。
3)\(x\) 端不在交错树中,\(y\) 端在交错树中的边 \(<i,j>\) ,它的 \(xy\) 的值有所增大。它原来不属于相等子图,仍不属于相等子图。
4)\(x\) 端在交错树中,\(y\) 端不在交错树中的边 \(<i,j>\) ,它的 \(xy\) 的值有所减小。它原来不属于相等子图,可能进入了相等子图,因而使相等子图得到了扩大。
5)到最后,\(x\) 端每个点至少有一条线连着,\(y\) 端每个点有一条线连着,说明最后补充完的相等子图一定有完备匹配。
所以,最大权子图子图是一步一步变大的
而显然的为了满足性质有: \(d = \min(lx[i]+ly[j]-mp[i][j])\) 其中 $ x_i$ 在交替树里,\(y_i\) 不在。
为啥?我们要扩大相等子图。
对于 \(d\) 的求解可以在求解交替树时完成
代码
bool find(int u)
{
visx[u] = 1;
for(int i = 1; i <= n; ++i)
{
if(visy[i]) continue;
if(lx[u] + ly[i] == mp[u][i])
{
visy[i] = 1;
if(!p[i] || find(p[i]))
{
p[i] = u;
return 1;
}
}
else d = min(d, lx[u] + ly[i] - mp[u][i]);
/* 在这里计算d */
}
return 0;
}
/* 当然了,如果可以匹配成功,那就不用d了 */
main:
for(int k = 1; k <= n; ++k)
{
while(1)
{
memset(visx, 0, sizeof visx);
memset(visy, 0, sizeof visy);
d = 1 << 30;
if(find(k)) break;
for(int i = 1; i <= n; ++i)
{
if(visx[i]) lx[i] -= d;
if(visy[i]) ly[i] += d;
}
}
}
简单口胡下时间复杂度:
循环 \(n\) 次 每次找增广修改 \(n\) 次顶标 用 \(n\) 次算 \(d\) 所以为 \(O(n^3)\) 但是怎么会跑到...
嵬
我没有那么好的天份 ,我只是多下点力气
关于时间复杂度的证明,好像 algorithm 里给了一个主定理之类的东西吧。去学习下,要肝日校了,可能会比较晚写