## 问题描述

  ​ 对于一个图\(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】一般图最大匹配——带花树的更多相关文章

  1. HDOJ 4687 Boke and Tsukkomi 一般图最大匹配带花树+暴力

    一般图最大匹配带花树+暴力: 先算最大匹配 C1 在枚举每一条边,去掉和这条边两个端点有关的边.....再跑Edmonds得到匹配C2 假设C2+2==C1则这条边再某个最大匹配中 Boke and ...

  2. ZOJ 3316 Game 一般图最大匹配带花树

    一般图最大匹配带花树: 建图后,计算最大匹配数. 假设有一个联通块不是完美匹配,先手就能够走那个没被匹配到的点.后手不论怎么走,都必定走到一个被匹配的点上.先手就能够顺着这个交错路走下去,最后一定是后 ...

  3. UOJ #79 一般图最大匹配 带花树

    http://uoj.ac/problem/79 一般图和二分图的区别就是有奇环,带花树是在匈牙利算法的基础上对奇环进行缩点操作,复杂度似乎是O(mn)和匈牙利一样. 具体操作是一个一个点做类似匈牙利 ...

  4. 【UOJ 79】 一般图最大匹配 (✿带花树开花)

    从前一个和谐的班级,所有人都是搞OI的.有 n 个是男生,有 0 个是女生.男生编号分别为 1,…,n. 现在老师想把他们分成若干个两人小组写动态仙人掌,一个人负责搬砖另一个人负责吐槽.每个人至多属于 ...

  5. 【UOJ #79】一般图最大匹配 带花树模板

    http://uoj.ac/problem/79 带花树模板,做法详见cyb的论文或fhq的博客. 带花树每次对一个未盖点bfs增广,遇到奇环就用并查集缩环变成花(一个点),同时记录每个点的Next( ...

  6. kuangbin带你飞 匹配问题 二分匹配 + 二分图多重匹配 + 二分图最大权匹配 + 一般图匹配带花树

    二分匹配:二分图的一些性质 二分图又称作二部图,是图论中的一种特殊模型. 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j ...

  7. URAL 1099. Work Scheduling (一般图匹配带花树)

    1099. Work Scheduling Time limit: 0.5 secondMemory limit: 64 MB There is certain amount of night gua ...

  8. HDU 4687 Boke and Tsukkomi (一般图匹配带花树)

    Boke and Tsukkomi Time Limit: 3000/3000 MS (Java/Others)    Memory Limit: 102400/102400 K (Java/Othe ...

  9. URAL1099 Work Scheduling —— 一般图匹配带花树

    题目链接:https://vjudge.net/problem/URAL-1099 1099. Work Scheduling Time limit: 0.5 secondMemory limit: ...

随机推荐

  1. 开源项目-网上公开http代理爬取、简单分类

    爬取网上公开免费代理(http/socks),解析入库,可满足需要切换IP的场景(爬虫.投票等)需求. 项目地址: https://github.com/Jwnie/proxyservice 1.采用 ...

  2. Selenium常用API用法示例集----下拉框、文本域及富文本框、弹窗、JS、frame、文件上传和下载

    元素识别方法.一组元素定位.鼠标操作.多窗口处理.下拉框.文本域及富文本框.弹窗.JS.frame.文件上传和下载 元素识别方法: driver.find_element_by_id() driver ...

  3. sqlsever 科学计数法 转标准值

    一.解决方案 2e-005 转成 0.00002 update 表名 set 列名=cast(列名 as float) as decimal(19,5)) where 列名 like  '%e%' 如 ...

  4. 在centos 6.8下安装docker

    1.检查自己的系统内核是不是64位系统,因为docker只能安装在64位系统中 命令: uname -a 结果 2.6.32-642.6.2.el6.x86_64 2.查看自己centos的版本 ca ...

  5. Spring中的注解@Service @Component @Controller @Repository区别

    @Service用于标注业务层组件, @Controller用于标注控制层组件(如struts中的action), @Repository用于标注数据访问组件,即DAO组件, @Component泛指 ...

  6. es2015及es2017对我们的编程方式造成了什么影响?

    记一些写代码中用得到的es6+语法,至于什么正则的拓展,数组的什么fill方法,对我们来说用处不大,就不提及了. 还有es6的import模块和class模块,这些在各种框架中都有体现,而且语法简单, ...

  7. mysql1 - 环境与体验

    一.准备工作 1.mac 软件包管理工具:homebrew 2.brew 如何使用?命令行 输入: brew 3.mac 下如何查看 mysql 目录? find /usr/local/ -iname ...

  8. R实战 第三篇:数据处理

    在实际分析数据之前,必须对数据进行清理和转化,使数据符合相应的格式,提高数据的质量.数据处理通常包括增加新的变量.处理缺失值.类型转换.数据排序.数据集的合并和获取子集等. 一,增加新的变量 通常需要 ...

  9. 和scikit-learn打个招呼

    1.先装对应的库.不能偷懒,都得装,不然飞不起来. pip install scikit-learn pip install numpy pip install scipy 2.测试如下代码. imp ...

  10. Py4j-RPC

    python 使用灵活.方便在科研中被广泛的使用,Numpy和SciPy等科学计算库使其拥有强大的计算方式.很多机器学习和深度学习的库也都采用了python,然而在大数据.后台开发中仍然较多的使用Ja ...