【learning】一般图最大匹配——带花树
## 问题描述
对于一个图\(G(V,E)\),当点对集\(S\)满足任意\((u,v)\in S\),均有\(u,v\in V,(u,v)\in E\),且\(S\)中没有点重复出现,我们称\(S\)为\(G\)的一个匹配,当且仅当\(|S|\)最大时,称\(S\)为\(G\)的最大匹配
那么要如何求解一个图的最大匹配呢?
特殊图上?
首先考虑特殊图的最大匹配问题,也就是很经典的二分图最大匹配,这个问题可以用匈牙利算法解决,这里就不再赘述具体的实现等细节问题,我们只回顾一下这个算法的核心思想
我们从一个未匹配点出发,寻找一个可行的匹配点。其中最为核心的思想就是寻找一条交错轨(起始边和结束边都为未匹配边,中间匹配边和为匹配边交替出现,且整条路径的长度为奇数)然后对整条路径进行增广
那么现在思考一个问题,是否可以将这个思想运用到一般图上呢?
答案显然是不行的。
我们分析一下,会得到一个结论,交错轨增广合法有效的前提是,图中不存在奇环(环中包含的点数为奇数的环),否则的话可能会增广出一个点同时是两个点的匹配点的情况,这显然是不合法的
而偶环显然不会存在这种情况(因为点数是偶数),这也说明了为什么交错轨增广只能在二分图中使用,因为二分图中不存在奇环。
那么现在问题就集中在奇环上面了,这也是带花树要解决的主要问题
花
既然奇环这个东西这么麻烦,那就干脆拎出来处理。给它一个比较好听的名字叫花
比如说上面蓝色的三个点组成的奇环在进行了缩点操作(之后会解释)之后可以被称作花,一朵花代表的奇环中还可以有花
考虑一下奇环的性质,我们可以得到这样的几个结论:
**1.对于包含\(k\)个节点的奇环,最大匹配数为\(\frac{k-1}{2}\),最大匹配点数为\(k-1\) **
2.没有被匹配到的点可以在奇环中的任意一个位置,不会影响结果
显然,因为我们要求的是最大匹配,每个暂时没有被匹配上的点我们都要在答案不会变坏的前提下尽可能的给它寻找一个匹配点,而由于奇环具有上面的两个性质,所以我们在遇到一个奇环的时候可以只考虑其中没有匹配上的那个点(因为一旦这个点确定了,其他的点的匹配情况也可以直接还原出来,方案唯一)
所以,我们可以将一个奇环缩成一个点(花)来考虑,且这样处理与直接考虑原图的最大匹配等价
那么这样我们就将奇环的问题完美处理了,“开花”之后的图中不会存在奇环,这样一来我们就可以沿用交错轨增广的思想了
具体实现
整个匹配的过程可以分为\(n\)个阶段(\(n\)为点数),我们从每个未匹配的点出发,bfs寻找一条增广路径。为了方便匹配,我们将点分为\(A\)类和\(B\)类两类,边走边将遍历到的点标号,需要注意的是,在匹配的每个阶段我们都需要重新给点标号(否则就达不到增广的目的了),且我们只考虑从\(A\)类点出发进行增广(可以类比二分图匹配)
因为中间的增广涉及到沿路上的匹配边反转,我们还需要一个数组\(rec\)来记录来的方向,也就是这个点是从哪个点走过来的
开始寻找增广路径之前,我们将起始点设为\(A\)类点,那么在增广的过程中可能遇到以下几种情况
找到了一个未匹配点
说明我们找到了一条增广路径,直接使用\(rec\)数组和\(match\)数组把过来的路走一遍然后反转一下就好,然后返回\(true\),说明找到了一个可行的匹配点
if (!match[u]){
rec[u]=v;
for (int x=u,nxt,mnxt;x;x=mnxt){
nxt=rec[x]; mnxt=match[nxt];
match[nxt]=x;
match[x]=nxt;
}
return true;
找到了一个未被分类但已经匹配了的点
我们将这个点标记为\(B\)类,将其匹配点标记为\(A\)类,然后将匹配点丢到队列里面去等待增广
找到了一个\(B\)类点
说明找到了一个偶环,而根据上面的分析,偶环是不会影响算法的正确性的,直接忽略就好了
找到了一个A类点
说明找到了一个奇环,那么我们需要做的事情是把这个奇环缩成一朵花,具体的操作就是寻找这两个点路径上面的"\(lca\)",然后将这两个点分别往\(lca\)缩(这里我们用并查集来实现),画个图直观理解一下是这样:
找\(lca\)写起来就是这样的,暴力往前跳,然后因为花中也可能有花,所以要用并查集的get_f获得代表点
int get_lca(int x,int y){
++T;
while (1){
if (x){
x=get_f(x);
if (vis[x]==T) return x;
vis[x]=T;
x=rec[match[x]];
}
swap(x,y);
}
}
合并也是十分简单粗暴,调用两次,直接沿着路径把两个点分别往\(lca\)缩就好了,由于奇环中所有的点都可能作为没有被匹配到的那个,所以都要标成\(A\)类然后丢到队列里面等待增广
void merge(int x,int y){
if (get_f(x)!=get_f(y)) f[x]=y;
}
void shrink(int x,int p){
int mx,nxt;
while (x!=p){
mx=match[x]; nxt=rec[mx];
if (get_f(nxt)!=p) rec[nxt]=mx;
if (tp[mx]==2)
tp[mx]=1,q.push(mx);
if (tp[nxt]==2)
tp[nxt]=1,q.push(nxt);
merge(x,mx);
merge(mx,nxt);
x=nxt;
}
}
找到了一个被缩在同一朵花里的点
这个就很简单啦直接忽略就好
至此,所有情况都已经被考虑完啦,由于整个算法分为\(n\)个阶段,每个阶段最多遍历整个图一次,每个点最多被缩成花\(n\)次,所以总体的复杂度是\(O(n^3)\)
\(UOJ79\)模板题,这里把完整的代码贴一下
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=510,M=124750;
struct xxx{
int y,next;
}a[M*2];
queue<int> q;
int h[N],rec[N],match[N],f[N],tp[N];
int vis[N];
int n,m,tot,ans,T;
void add(int x,int y);
int get_f(int x){return f[x]=f[x]==x?f[x]:get_f(f[x]);}
bool agu(int st);
void init(int st);
int get_lca(int x,int y);
void shrink(int x,int p);
void solve();
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
int x,y;
scanf("%d%d",&n,&m);
memset(h,-1,sizeof(h));
tot=0;
for (int i=1;i<=m;++i){
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
solve();
}
void add(int x,int y){
a[++tot].y=y; a[tot].next=h[x]; h[x]=tot;
}
bool agu(int st){
init(st);
int u,v,lca;
while (!q.empty()){
v=q.front(); q.pop();
for (int i=h[v];i!=-1;i=a[i].next){
u=a[i].y;
if (match[u]==v) continue;
if (get_f(u)==get_f(v)) continue;
if (tp[u]==2) continue;
if (tp[u]==1){//奇环
lca=get_lca(u,v);
if (get_f(u)!=lca) rec[u]=v;
if (get_f(v)!=lca) rec[v]=u;
shrink(v,lca);
shrink(u,lca);
}
else if (!match[u]){
rec[u]=v;
for (int x=u,nxt,mnxt;x;x=mnxt){
nxt=rec[x]; mnxt=match[nxt];
match[x]=nxt;
match[nxt]=x;
}
return true;
}
else{
rec[u]=v;
tp[u]=2;
tp[match[u]]=1;
q.push(match[u]);
}
if (tp[u]) continue;
}
}
return false;
}
void init(int st){
while (!q.empty()) q.pop();
for (int i=1;i<=n;++i)
f[i]=i,vis[i]=false,tp[i]=0,rec[i]=0;
q.push(st);
tp[st]=1;
}
int get_lca(int x,int y){
++T;
while (1){
if (x){
x=get_f(x);
if (vis[x]==T) return x;
vis[x]=T;
x=rec[match[x]];
}
swap(x,y);
}
}
void merge(int x,int y){
if (get_f(x)!=get_f(y)) f[x]=y;
}
void shrink(int x,int p){
int mx,nxt;
while (x!=p){
mx=match[x]; nxt=rec[mx];
if (get_f(nxt)!=p) rec[nxt]=mx;
if (tp[mx]==2)
tp[mx]=1,q.push(mx);
if (tp[nxt]==2)
tp[nxt]=1,q.push(nxt);
merge(x,mx);
merge(mx,nxt);
x=nxt;
}
}
void solve(){
for (int i=1;i<=n;++i)
if (!match[i]) agu(i);
for (int i=1;i<=n;++i)
if (match[i]>i) ++ans;
printf("%d\n",ans);
for (int i=1;i<=n;++i)
printf("%d ",match[i]);
}
【learning】一般图最大匹配——带花树的更多相关文章
- HDOJ 4687 Boke and Tsukkomi 一般图最大匹配带花树+暴力
一般图最大匹配带花树+暴力: 先算最大匹配 C1 在枚举每一条边,去掉和这条边两个端点有关的边.....再跑Edmonds得到匹配C2 假设C2+2==C1则这条边再某个最大匹配中 Boke and ...
- ZOJ 3316 Game 一般图最大匹配带花树
一般图最大匹配带花树: 建图后,计算最大匹配数. 假设有一个联通块不是完美匹配,先手就能够走那个没被匹配到的点.后手不论怎么走,都必定走到一个被匹配的点上.先手就能够顺着这个交错路走下去,最后一定是后 ...
- UOJ #79 一般图最大匹配 带花树
http://uoj.ac/problem/79 一般图和二分图的区别就是有奇环,带花树是在匈牙利算法的基础上对奇环进行缩点操作,复杂度似乎是O(mn)和匈牙利一样. 具体操作是一个一个点做类似匈牙利 ...
- 【UOJ 79】 一般图最大匹配 (✿带花树开花)
从前一个和谐的班级,所有人都是搞OI的.有 n 个是男生,有 0 个是女生.男生编号分别为 1,…,n. 现在老师想把他们分成若干个两人小组写动态仙人掌,一个人负责搬砖另一个人负责吐槽.每个人至多属于 ...
- 【UOJ #79】一般图最大匹配 带花树模板
http://uoj.ac/problem/79 带花树模板,做法详见cyb的论文或fhq的博客. 带花树每次对一个未盖点bfs增广,遇到奇环就用并查集缩环变成花(一个点),同时记录每个点的Next( ...
- kuangbin带你飞 匹配问题 二分匹配 + 二分图多重匹配 + 二分图最大权匹配 + 一般图匹配带花树
二分匹配:二分图的一些性质 二分图又称作二部图,是图论中的一种特殊模型. 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j ...
- URAL 1099. Work Scheduling (一般图匹配带花树)
1099. Work Scheduling Time limit: 0.5 secondMemory limit: 64 MB There is certain amount of night gua ...
- HDU 4687 Boke and Tsukkomi (一般图匹配带花树)
Boke and Tsukkomi Time Limit: 3000/3000 MS (Java/Others) Memory Limit: 102400/102400 K (Java/Othe ...
- URAL1099 Work Scheduling —— 一般图匹配带花树
题目链接:https://vjudge.net/problem/URAL-1099 1099. Work Scheduling Time limit: 0.5 secondMemory limit: ...
随机推荐
- Hive入门教程
Hive 安装 相比起很多教程先介绍概念,我喜欢先动手装上,然后用例子来介绍概念.我们先来安装一下Hive 先确认是否已经安装了对应的yum源,如果没有照这个教程里面写的安装cdh的yum源http: ...
- 加快compser install 和update的方法
加快compser install 和update的方法: 可以进入composer国内镜像里面进行参考 如下是修改composer.json文件来实现(在json配置的最后加上如下代码) " ...
- Java 面向对象三大特征之一: 多态
多态与类型转换 子类重写父类方法 1)位置:子类和父类中有同名的方法 2)方法名相同,返回类型和修饰符相同,参数列表相同 方法体不同 多态的优势和应用场合 多态:同一个引用类型,使用不同的 ...
- C/C++语言简介之编程开发
一.编译器 GCC:GNU组织开发的开源免费的编译器. MinGW:Windows操作系统下的GCC. Clang:开源的BSD协议的基于LLVM的编译器. Visual C++:Microsoft ...
- #pragma预处理命令
#pragma comment(lib,"XXX.lib") 表示链接XXX.lib这个库,和在工程设置里写上XXX.lib的效果一样. #pragma comment(linke ...
- (!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]一行js代码的原理分析
再说这行代码之前,咱们先来预习一下知识. 我们都知道计算机操作系统分为32位或者64位.那么这个32位或64位指的是什么意思呢?其实,要想解释它并不难,其实这就是计算机处理数据的机制,32位表示计算机 ...
- SyntaxError: Missing parentheses in call to 'print'
C:\Users\konglb>python Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 17:26:49) [MSC v.1900 32 bit (I ...
- Eviews 9.0新功能——估计方法(ARDL、面板自回归、门限回归)
每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 9.2 估计功能 eviews9.0下载链接: ...
- HI3531编译helloworld,执行错误
若在嵌入式系统中执行某文件出现如下错误: -/bin/sh: XXX: not found 一般是因为缺少库文件,解决方法有2: 1,文件系统的busybox编译时使用动态编译方式 2,或编译该文件的 ...
- FusionCharts 3D环饼图
1.设计静态页面 Doughnut.html: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"& ...