隐藏页面特效

解决一般图最大匹配的利器——带花树浅析

1|0Preface


好像很久没有学过新算法了罢,或者说没有写过板子的博客了

前段时间在DS专题中可能有学过吉司机线段树,不过由于那个重在思想而且最关键的复杂度证明不太懂也就没有专门写篇博客了

这次在图论专题中补上了OI时一直没学的带花树,不过好像这个科技现在除了做板子题外还没什么太大的用处的说

个人学习自某dalao的Blog,感觉讲的十分清晰易懂,狠狠地好评


2|0前置知识


在二分图的匈牙利算法中,其核心就是寻找尽可能多的增广路过程,这里我们系统性的给出一些概念的定义

  • 孤立点:指一个u,没有与其匹配的点v
  • 交错路(alternating path),指一条图中的路径,满足上面的边在匹配中交替出现
  • 增广路(augmenting path),指一条开始并结束于不相同的孤立点的交错路

由于增广路的定义,上面不是匹配的边比是匹配的边多,因此增广路的边条数为奇数

而如果图中没有增广路了,则我们找到了一个最大匹配;否则我们通过增广操作一定可以获得更大的匹配

因此不管是二分图还是一般图,找最大匹配算法的核心都是一直执行增广操作直至不能再增广


3|0关于花的思考


3|1关于花的定义


带花树的核心,莫过于其独特的缩环为花的操作,我们先给出形式化的花的定义:

一朵花B是图G中的一个包含2k+1个点的奇环,其中k条边在匹配M中,并存在从环上任意一点v(也叫花根)到某个孤立点w的交错路(也叫花茎

然后我们就可以定义图G为将花B缩为点后的新图,M为此时G中的匹配

不难发现G中含有M的增广路当且仅当G中含有M的增广路,且M在图G中的增广路P可以通过展开收缩的花来还原得到G中的匹配M

3|2如何找到图中的花


  1. 从一个孤立的点w开始遍历图
  2. 从孤立点w开始遍历,并标记w为o型点(out of M)
  3. 交替地用i和o标记结点,保证无两个相邻结点有相同标记
  4. 如果找到两个相邻结点均含有标记o,那么我们就找到了一个奇环与一朵花

3|3关于花的一些细节


关于前面提到的新图和原图的增广路等价,形式化的表示就是如果在P中存在一条uVBw的增广路,这条增广路在原图中可以被替换为u(uw)w,其中u,w在花B

而与此同时uw的选择必须保证此时新增广路仍然是交替匹配的,这个用图会比较好理解:

还有一种比较特殊的情况就是当PVB结束时,我们就要把这条增广路替换为u(uv),其中u,v在花B

而与此同时uv的选择必须保证此时新增广路仍然是交替匹配的,还是一图胜千言:

因此不论对于哪种情况,把奇环缩成点都能对应处理,这便是带花树算法的核心

3|4一些思考


为什么只考虑缩奇环而不缩偶环,因为偶环的标记是不会发生冲突的

即可以从某个点出发,对一个偶环上的点全部匹配后回到原点,这显然是最优的

而实现的时候我们不需要真正实现缩花的过程,而是隐式地只记录每个点属于哪个花即可


4|0具体实现


我们对于每个点维护以下信息:

  • type,表示这个点的类型,0为o类点,1为i类点,1为未访问
  • match,表示这个点匹配的点,注意match具有双向性,即若match[x]=y,则必有match[y]=x
  • pre,表示这个点在交错路上相邻但不和当前点构成匹配的点,注意pre在非花的增广路上是单向的,不过由于在花上增广的方向不定,因此pre是双向的
  • father,表示这个点所属的花,若不属于任何花则置为本身

然后算法的大致过程如下:

  1. 若寻找到一个未被标记的未匹配点:将其标记为i型点,找到一条增广路,更新并维护该增广路的信息,完成增广
  2. 若寻找到一个未被标记的点,但其已经被匹配,将其标记为i型点,并将其匹配的点标记为o型点,加入队列
  3. 若寻找到一个已经被标记的i型点,说明此时构成偶环,直接无视
  4. 若寻找到一个已经被标记的o型点,说明此时构成奇环且构成花,在当前扩展出的交错路上找到其公共祖先lcalca此时为花根,沿着两侧的花边爬到花根,将路径上的结点的father指针更新,标记全部更新为o,并将pre指针变为双向指针

5|0板子


模板题见:Luogu P6113 【模板】一般图最大匹配

#include<cstdio> #include<iostream> #include<vector> #include<queue> #include<cstring> #define RI register int #define CI const int& using namespace std; const int N=1005; int n,m,x,y,match[N],ans; vector <int> v[N]; namespace Blossom_Algorithm { int fa[N],vis[N],pre[N],tp[N]; queue <int> q; inline int getLCA(int x,int y) { static int idx=0; ++idx; x=fa[x]; y=fa[y]; while (vis[x]!=idx) { if (x) vis[x]=idx,x=fa[pre[match[x]]]; swap(x,y); } return x; } inline void blossom(int x,int y,int lca) { while (fa[x]!=lca) { pre[x]=y; y=match[x]; if (tp[y]==1) tp[y]=0,q.push(y); fa[x]=fa[y]=fa[lca]; x=pre[y]; } } inline bool Augument(CI s) { for (RI i=1;i<=n;++i) fa[i]=i; memset(tp,-1,sizeof(tp)); q=queue <int>(); q.push(s); tp[s]=0; while (!q.empty()) { int now=q.front(); q.pop(); for (int to:v[now]) if (!~tp[to]) { if (pre[to]=now,tp[to]=1,!match[to]) { for (int x=now,y=to;y;x=pre[y]) match[y]=x,swap(match[x],y); return 1; } tp[match[to]]=0; q.push(match[to]); } else if (!tp[to]&&fa[now]!=fa[to]) { int lca=getLCA(now,to); blossom(now,to,lca); blossom(to,now,lca); } } return 0; } }; using namespace Blossom_Algorithm; int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); RI i; for (scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x); for (i=1;i<=n;++i) if (!match[i]) ans+=Augument(i); for (printf("%d\n",ans),i=1;i<=n;++i) printf("%d ",match[i]); return 0; }

6|0Postscript


带花树的复杂度上界在于枚举孤立点时要把所有点入队,如果找到花的话就要遍历整朵花,因此总复杂度为O(n3)

而据说带花树还有扩展可以用来解决一般图的带权匹配问题,这个有点恐怖了就先不管了


__EOF__

本文作者hl666
本文链接https://www.cnblogs.com/cjjsb/p/17367528.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
posted @   空気力学の詩  阅读(156)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示