【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: ...
随机推荐
- 中小研发团队架构实践之应用监控Metrics
一.Metrics简介 应用监控系统Metrics由Metrics.NET+InfluxDB+Grafana组合而成,通过客户端Metrics.NET在业务代码中埋点,Metrics.N ...
- Linux-PATH_环境变量
PATH变量 是linux系统里的一个环境变量,系统已经定义好了,我们不需要再定义. 作用: 是linux里使用的命令都存在在PATH变量后面指定的目录下,我们使用命令 ...
- object类的equals方法简介 & String类重写equals方法
object类中equals方法源码如下所示 public boolean equals(Object obj) { return this == obj; } Object中的equals方法是直接 ...
- jquery序列化serialize()方法空格变为+问题解决参考方法
$("#sendNoticeData-form").serialize();会在value中存在空格的地方转化为+符合.比如:name:tiwax aaa序列化后为tiwax+aa ...
- Date 类 02
Date类 在JDK1.0中,Date类是唯一的一个代表时间的类,但是由于Date类不便于实现国际化,所以从JDK1.1版本开始,推荐使用Calendar类进行时间和日期处理.这里简单介绍一下Date ...
- 老男孩Python全栈开发(92天全)视频教程 自学笔记21
day21课程内容: json: #序列化 把对象(变量)从内存中 编程可存储和可传输的过程 称为序列化import jsondic={'name':'abc','age':18}with open ...
- Quartz基本使用
1.Quartz概述:简单的说就是java的一个定时任务的框架,没有那么复杂的解释,跟前端的定时器一样.在了解它之前,首先熟悉几个概念. 2.基本概念 2.1 Job:表示一个工作,要执行的具体内容. ...
- (2018干货系列五)最新UI设计学习路线整合
怎么学UI全链路设计 全链路设计师是参与整个商业链条,为每个会影响用户体验的地方提供设计的可解决方案,最后既满足了商业目标,又提升了产品的用户体验和设计质量,与平面设计.UI设计彻底区分开来,是真正的 ...
- Linux Framebuffer驱动剖析之二—驱动框架、接口实现和使用
深入分析LinuxFramebuffer子系统的驱动框架.接口实现和使用. 一.LinuxFramebuffer的软件需求 上一篇文章详细阐述了LinuxFramebuffer的软件需求(请先理解第一 ...
- 高级DirectDraw
使用高彩模式 上一章中说了可以用16位的色彩深度,但是16位的色彩深度的数据表示模式可以有两种:Alpha.5.5.5(or X.5.5.5) 和 5.6.5(这是16位色彩最常用的).对于使用哪种1 ...