【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: ...
随机推荐
- 设为首页/加入收藏JS代码
传统网站常用的设为首页/加入收藏js代码 <a href="javascript:void(0)" onclick="sethome(this,window.loc ...
- LaTeX 各种命令,符号
函数.符号及特殊字符 声调 语法 效果 语法 效果 语法 效果 \bar{x} \acute{\eta} \check{\alpha} \grave{\eta} \breve{a} \ddot{y} ...
- ASP.NET Core 2.0 : 五.服务是如何加载并运行的, Kestrel、配置与环境
"跨平台"后的ASP.Net Core是如何接收并处理请求的呢? 它的运行和处理机制和之前有什么不同? 本章从"宏观"到"微观"地看一下它的 ...
- XAMPP禁止目录浏览的方法
XAMPP是目前比较流行Web服务器套件,集成了Apache.MySQL.PHP.PERL.FTP等各种软件包.但是细心的人可以发现,XAMPP安装完成后,默认是可以目录浏览的,这有些不安全.如果需要 ...
- Java集合框架(三)—— List、ArrayList、Vector、Stack
List接口 List集合代表一个有序集合,集合中每一个元素都有其对应的顺序索引.List集合容许使用重复元素,可以通过索引来访问指定位置的集合对象. ArrayList和Vector实现类 Arra ...
- hihoCoder 1523 数组重排2 贪心
题意:给定一个1-N的排列A1, A2, - AN,每次操作小Hi可以选择一个数,把它放到数组的最左边. 请计算小Hi最少进行几次操作就能使得新数组是递增排列的. 思路:最后的序列是递增的,那么必定满 ...
- Java中子类能继承父类的私有属性吗?
前段时间去听老师讲课的时候,老师告诉我子类是可以继承父类所有的属性和方法的.当时我是极其疑惑的,因为之前学校考试时这个考点我记得很清楚:子类只能继承父类的非私有属性和方法.老师给我的解释是这样的--先 ...
- Centos安装jdk8
1.下载jdk1.8的tar cd /usr/local/src #切换到该目录下 wget url #下载jdk8的tar包 2.下载完成后解压tar包 tar -zxvf jdk-8u152-li ...
- linux lvm管理扩展 RAID磁盘阵列管理
LVM逻辑卷将多个物理分区/磁盘从逻辑上组合成一个更大的整体,从其中划分出不同的逻辑分区,逻辑分区的大小可以根据需要扩大,缩减!!!!/boot用来存放引导文件,不要基于LVM创建 PV(物理卷)物理 ...
- C++ string数据类型的实现
#include <cstdlib> #include <cstring> class Mystring { public: Mystring(const char * pda ...