一般图最大匹配——带花树

所谓花,就是如下图所示的一个奇环: 1

本文中粗边代表现在的匹配边,细边代表该点的前驱(后文会讲解前驱是什么,现在只需要知道每个点和它的前驱在原图中一定是有边的)。

如图所示,一朵包含2k+1个点的花一定至多包含k条匹配边,于是总会剩下一个未匹配的点,上图中即为1号点。

那么我们可以发现,如果有另外一个点想要与花中的某个点v匹配,那么有两种情况:1、v是未匹配的点(即1号点),那么直接与v匹配即可。2、v是已经匹配的点,这时只要将花中的匹配状况修改,使得v变成未匹配的那个点即可。

综上所述,只要花中的点没有向外匹配,我们总是可以使得外部的一个点和花中任意一个点匹配,因此花的性质和点其实很相似。我们将花缩成一个点来处理,就可以解决出现奇环的问题。以上思想就是带花树算法的核心。

==================总之分割一下好了==================

带花树算法的过程其实和bfs版本的匈牙利是很相似的,都是找出一个交错树,交错树可能长这样(注意每个蓝色点可能有多个橙色儿子,但是每个橙色点只能有一个蓝色儿子): 2

其中1号点就是我们尝试增广的节点,在这里我们给每一个节点一个type值,若该点不在交错树中,它的type值为0,否则为12。上图中我们用蓝色点代表type=1的点,橙色点代表type=2的点,不难看出type值的不同其实代表了一种类似于二分图的关系,每个点在交错树中只和type值不同的点相连。当我们没有找到奇环的时候,type值和二分图是等价的。

那么仿照匈牙利的过程,我们将尝试增广的点vtype值设为1并开始增广,假设当前处理的点为u

1、如果typeu=0,就代表它不在交错树中:

u已经有匹配时,我们就扩展这棵交错树,将typeu的值设为2(因为其和type值为1v相邻),并将typematchu的值设为1(同理)。这时我们就可以把matchu塞进队列里了,如果能够沿着matchu找到增广路的话我们就可以让matchu匹配那个增广的点并将uv匹配,这样就使匹配数增加了1。同时我们将u的前驱(用preu表示)设置为v,这是为了方便在找到增广路以后一路返回修改匹配。

u并没有匹配时,我们就成功找到了一条增广路,此时沿着由prematch连成的边一路修改就增广完成了,返回。

2、如果typeu=2,代表你找到了一个偶环,并没有什么用,就跳过这个点。

3、这里是最重点的,如果typeu=1,代表你找到了一个奇环,这就代表你的type值不再等价于二分图了,我们这个时候就可以开始“缩花”操作,将我们找到的奇环缩成一个点。让我们具体的考虑一下:

首先,快速找到哪些点在这个奇环中,显然uv一定都出现在交错树上(type不为0),结合上面的那张图考虑,奇环的范围就是两个点在交错树上的链中包含的所有点,因此我们需要找到这两个点的lca,这里直接采用暴力向上跳的做法即可。

找到以后怎么连接pre边呢?我们参考一下文章开头的结构,可以发现此时的lca一定就是那个花中唯一的没有匹配或者匹配到外面节点的1号点。因此感性思考一下,我们应该“诱导”其他所有的点通过prematch边一路走走到lca上,因此将除了与lca相连的pre边外其他的pre边都改为双向边,并将uv也改成双向边即可达到这个目的。

最后做一点扫尾工作,我们可以通过并查集将奇环缩成一个点(因此在普通增广的时候也要考虑并查集的情况),不妨用lca做这朵花的代表。同时再考虑一下这个新点的type值应该设为什么,因为每个橙色点最多只有一个儿子(它的匹配点),因此lca一定是蓝色点,因此新点的type值为1,所以要注意将花中所有type值为2的点修改为1并且加入队列。

当我们找不到新的点时,本次增广失败。

===============分割分割================

以上就是一般图最大匹配的增广过程,注意在每次增广之前,pretype以及并查集都是要初始化的。将并查集的复杂度看作常数,则每次增广至多是O(m)的,一共需要增广O(n)次,因此带花树算法的复杂度和匈牙利一样,都是O(nm),当然,在实践中带花树算法跑得一般会比理论上界快很多。

相信在熟练理解带花树的过程后一定能写出代码,因此为了不对读者造成思维定式影响这里就不贴代码了。完结撒花~