带花树学习笔记

前置知识:匈牙利算法 luogu P3386 【模板】二分图最大匹配

P6113 【模板】一般图最大匹配

算法本身还是挺好理解的,主要是实现时细节巨多。

首先可以想到使用匈牙利算法。但是二分图保证了图中没有奇环,匈牙利的正确性才能保证。
否则匹配可能不合法。(可以手玩一下)
而对于一般图 我们有带花树算法:

首先 把最难搞的奇环搞掉:我们给奇环起个新的名字:
其次 我们发现 对于一个奇环 假如它有 \(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;
}
posted @ 2023-12-25 08:56  xiang_xiang  阅读(30)  评论(0编辑  收藏  举报