带花树学习笔记
Daniel_yuan · · 个人记录
在下文中,虚边默认是非匹配边,实边默认是匹配边。
路径取反的意思是,把路径上点匹配边变成非匹配边,非匹配边变成匹配边。
核心思想
类似于二分图匹配,一般图匹配也是找交替链形成的增广路不断增广,但是因为图中可能存在奇环,二分图中无脑交替跳边和记忆化都不能使用。
如图。如果像二分图一样无脑交替跳边,可能会走出图中红绿交替的路径,这样看似找到了一条增广路,但实际上这条路不能进行增广,因为
而按照二分图的做法,当我们沿着
即使这样,我们还是可以从二分图找增广路的思路出发,尝试加上一些别的操作使得一般图也可以正确地找到增广路。
首先考虑处理奇环。不难发现的是,只有形如边数为
可以发现,在这种限制下,环中有且仅有一个点可以和环外匹配,且环内任意点都可以成为这个点。
如图,这是我们需要的一个奇环。这个时刻有且仅有
也就是说环这个整体和一个点的本质没有什么区别——和外界有且仅有一个匹配。
所以这时候我们直接把这个环缩成一个点即可,这个过程被称作开花。其中最上面的点叫做花根(然后图上就有花了,而找增广路会形成一个交替链构成的树,所以带花树就是有花的树)
这就是带花树的核心思想——在找增广路的途中如果碰到了一个奇环就把奇环缩成一个点,然后继续找。找到增广路之后就直接增广,如果一个点就是点就直接改匹配边,如果一个点是花就展开,然后在内部把它向外界的连边调整到需要的状态再改匹配边。
完结撒花。
具体实现
带花树的思想是非常直接的——分析奇环的性质然后做出相应措施。但是带花树算法的代码实现复杂度比较高。
我们把带花树的算法流程分成找树——开花——增广三个流程。
找树
首先我们选择的找增广路的方式应该是 bfs 而不是 dfs。因为在找到花之后,我们需要缩点,缩点后如果用 dfs 边的处理会比较麻烦。
在 bfs 的时候我们给每个点一个 tag,表示这个点的属性,它的意义是这个点的父边是匹配边还是非匹配边,假设前者为
在扩张交替树的时候,就可以直接通过下一个点的 tag 来判断交替树接下来的状态。一共有以下几种情况:
- 下一个点没有 tag,且它没有匹配。那么就找到了一条增广路。
- 下一个点没有 tag,且它有匹配。那么直接把它和它的匹配点加入交替树,并且把它的匹配点加入 bfs 的队列(因为边状态的交替性,所以放下一个点的匹配点)。
- 下一个点有 tag,且它和当前点在同一个花中。花内只需要留下一个大环即可,剩下的边无所谓,直接忽略。
- 下一个点有 tag,且它和当前点的 tag 不同。那么就找到了一个偶环,直接忽略。
- 下一个点有 tag,且它和当前点的 tag 相同。那么就找到了一个奇环,直接开花。
这样我们就可以通过 bfs 求交替树来找增广路。
开花
当碰到一个奇环的时候我们需要开花。为了方便,缩点我们并不进行直接缩点,而是用并查集维护每个点当前的花根是什么。为了方便,每个点一开始就是一朵花,它自己是花根。
在找到奇环之后我们需要开花。假设最后的一条边是
找到花根
那么我们直接扫暴力从
这样我们就完成了开花的操作。
增广
其实上面几个都比较简单,主要难点是在增广上。
假设点
在扩展 bfs 交替树的时候,假设当前在扫
难点是在开花的时候怎么维护
因为在没有奇环的时候,增广可以看成是在交替树上一条叶子到根的链。深度为奇数的点往上跳是跳
但是在花上不一样,因为走花有两种走法,且两种走法对于一个点来说走的边的类型是不同的。所以在开花的时候,我们需要遍历花,并且更新路径上面点的
如图,增广路是
所以在开花的时候把向下走的
但这样会产生一个问题,就是更新
如图,这里交替树视为不包括
————还没写完先咕着————