浅谈二分图匹配

我觉得我现在还可以

正文

本文权当作学习二分图匹配的一些笔记,个人风格极为严重,请勿深究语文

匈牙利

本文不介绍算法的步骤,因为可以 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说的),客观的说,其实各有千秋吧。

  1. 运行时间:几乎相等,因为没有毒瘤可以把匈牙利卡到 \(O(nm)\),而且 ISAP 的常数肯定比匈牙利大呀.
  2. 代码实现难度:匈牙利完爆各种最大流
  3. 写完的成就感:最大流完爆匈牙利
  4. 运行空间:会卡这个?

真正的代码

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;
}

然后我们就可以用它来搞各种与二分图有关的东西。

下文介绍几种别的扩展

  1. 最少点覆盖
  2. 最少边覆盖
  3. 最大独立子集
  4. 最少不相交路径覆盖

最小点覆盖

反证一下,他应该就是最大匹配数,因为如果有边并未覆盖。那么他的左右端点应该都没被选,所以就不是最大匹配了,矛盾

所以:最小点覆盖 = 最大匹配数

模板: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 里给了一个主定理之类的东西吧。去学习下,要肝日校了,可能会比较晚写

猜你喜欢

转载自www.cnblogs.com/zhltao/p/12549489.html
今日推荐