带花树学习笔记
前置知识:匈牙利算法 luogu P3386 【模板】二分图最大匹配
算法本身还是挺好理解的,主要是实现时细节巨多。
首先可以想到使用匈牙利算法。但是二分图保证了图中没有奇环,匈牙利的正确性才能保证。
否则匹配可能不合法。(可以手玩一下)
而对于一般图 我们有带花树算法:
首先 把最难搞的奇环搞掉:我们给奇环起个新的名字:花
其次 我们发现 对于一个奇环 假如它有 \(2k+1\) 个点 匹配后必然是 \(2k\) 个点互相匹配 剩下一个和外面匹配最优。
那么 这个奇环其实和一个点也没啥区别 把它缩成一个点即可。
这个过程我们称为开花
我们外层跑bfs+匈牙利 内层由并查集实现缩环
然后就没了。、
然而代码巨难写。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define endl '\n'
#define gc cin.get
#define pc cout.put
const int N=1e3+5;
const int M=3e5+5;
const int inf=0x3f3f3f3f;
const int mod=997;
const int base=13131;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int n,m,match[N],pre[N],col[N],fa[N],dfn[N],num,ans;
int head[N],nxt[M],to[M],cnt;
inl void add(int u,int v){
nxt[++cnt]=head[u];
to[cnt]=v;
head[u]=cnt;
}
inl int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
queue<int>q;
inl int lca(int x,int y){
++num;x=find(x),y=find(y);
while(dfn[x]^num){
//在路上打标记 直到两条路径碰头
dfn[x]=num;
x=find(pre[match[x]]);
if(y)swap(x,y);//注意:可能花根没有前驱 换完之后会改到x=0的情况 特判让他退出
}
return x;
}
inl void blossom(int x,int y,int lca){
while(find(x)^lca){
pre[x]=y,y=match[x];
col[y]=1;q.push(y);
fa[x]=fa[y]=lca;
x=pre[y];
}
//这个就不具体说了 实际上是如下情况:
o
(双向pre) / \(双向pre)
o o
(双向match) | |(双向match)
o — o
(双向pre)
//可以发现 无论哪个方向 都能循环遍历一遍(方便匈牙利修改)
}
inl int solve(int x){
for(int i=1;i<=n;i++)fa[i]=i;
memset(pre,0,sizeof pre);
memset(col,0,sizeof col);
while(!q.empty())q.pop();
q.push(x);col[x]=1;//黑白染色:黑点1,白点2
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(find(x)==find(y)||col[y]==2)continue;
//在一朵花内/是偶环 不用管
if(!col[y]){
col[y]=2,pre[y]=x;//染白
//注意:黑点有匹配 前置点为 match[x]
// 白点用pre记录
if(!match[y]){//无匹配
for(int p=y,lst;p;p=lst)
lst=match[pre[p]],match[pre[p]]=p,match[p]=pre[p];
//匈牙利基本操作:增广&匹配
return 1;
}
col[match[y]]=1;q.push(match[y]);
//有匹配:染黑&重新找
}else{
//找到奇环:开花
int w=lca(x,y);//找花根
blossom(x,y,w);blossom(y,x,w);
//改花中的路径 方便最后找前驱改匹配
}
}
}
return 0;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
n=read();m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read();
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)if(!match[i])ans+=solve(i);
cout<<ans<<endl;
for(int i=1;i<=n;i++)cout<<match[i]<<' ';
return 0;
}