对于一般的二分图匹配我们肯定会想到匈牙利算法,但是如果图中出现奇环怎么办?此时匈牙利算法就不可以了,就需要另一个算法:带花树算法
主要就是为了解决奇环的问题
我们匹配时会发现,如果存在奇环,传统的匈牙利算法在一个奇环里至少有一个点不能匹配,那么干脆就把这个奇环缩成一个点(也叫开花,这就是算法名字的由来),在处理到奇环的时候把它缩成一个点,路径取反的时候再暴力展开一个个取反。
缩点用并查集来维护

流程:

参考代码
我们给所有点黑白染色。假设开始增广的点是黑点。把所有黑点压进队列中顺次处理。对于一个黑点u,找与他相邻的点v,会出现一下四种情况:

1、u,v已经被缩成一个点了(这两个点在一朵花里),跳过。

2、v是白点,说明已经被匹配了,也就是偶环,跳过。

3、v还没有被染色。那就先把这个点染成白的,然后尝试与u匹配。如果v还没有匹配就匹配上,增广成功,然后沿增广路取反。如果v已经被匹配了,那么匹配他的点就是个黑点,染色,然后压进队列。

4、v也是黑点。这时候染色发生了冲突,说明遇见了奇环。这时候就需要找到两个点在带花树中的lca,然后把这整个环缩成一个点。(直接开花。)

缩点(开花)过程:
1、找x和y的LCA(的根)L。找LCA可以用各种方法。。。直接朴素也行。
2、在pre数组中把x和y接起来(表示它们形成环了!)
3、从x、y分别走到L,维护并查集使得变成一棵树,同时沿路把pre数组连接起来。

题目:

UOJ79 一般图最大匹配
从前一个和谐的班级,所有人都是搞OI的。有 n 个是男生,有 0 个是女生。男生编号分别为 1,…,n。
现在老师想把他们分成若干个两人小组写动态仙人掌,一个人负责搬砖另一个人负责吐槽。每个人至多属于一个小组。
有若干个这样的条件:第 v 个男生和第 u 个男生愿意组成小组。
请问这个班级里最多产生多少个小组?

题解:

就是带花树的模板题

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 510;
const int M = 3e5+10;
struct node{ int to,nxt; }g[M];
int head[N],cnt;
int vis[N],match[N],f[N],pre[N],Id,id[N];
//vis[i]: 0(未染色) 1(黑色) 2(白色)
//match[i]: i的匹配点
//f[i]: i在带花树中的祖先
//pre[i]: i的非匹配边的另一点 
//id: 找LCA用 
int n,m,ans,u,v;
queue<int> q;
void Init()
{
    Id=ans=cnt=0;
    for(int i=1;i<=n;i++)
        head[i]=-1,id[i]=match[i]=0;
}
void add(int u,int v){ g[cnt]=node{v,head[u]},head[u]=cnt++; }
int getf(int x){ return f[x]==x?x:f[x]=getf(f[x]); }
//查询x和y在带花树中的LCA 
int LCA(int x,int y)
{
    //沿着增广路向上找lca 
    for(++Id;;swap(x,y))//x,y交替向上 
        if(x)
        {
            x=getf(x);//有可能环中有环(花中有花),所以用并查集找祖先,只处理祖先节点 
            if(id[x]==Id) return x; //x,y在同一环中,一定会找到已被编号的点,该点即为LCA。 
            else id[x]=Id,x=pre[match[x]];//给点编号,并沿着非匹配边向上找        
        }
}
//缩点(开花 
void blossom(int x,int y,int l)//,将x、y到LCA(l)路径中的点,缩为一点int l)
{ 
    while(getf(x)!=l)
    {
        //增广路取反 
        pre[x]=y;
        y=match[x];
        //如果x、y的奇环中有白点,将其染为黑点,放入队列,让其去找不是环中的匹配点    
        if(vis[y]==2) vis[y]=1,q.push(y);
        //只改变是根的点 
        if(getf(x)==x) f[x]=l;
        if(getf(y)==y) f[y]=l;
        //增广路取反 
        x=pre[y];
    } 
}
bool aug(int s)
{
    //每次都以s为起点bfs,建带花树 
    for(int i=1;i<=n;i++)
        vis[i]=pre[i]=0,f[i]=i;    
    while(!q.empty()) q.pop();

    q.push(s),vis[s]=1;
    while(!q.empty())
    {
        u=q.front();q.pop();
        for(int i=head[u];~i;i=g[i].nxt)
        {
            v=g[i].to;
            //如果已经在同一个环(花)中或者是白点(意为这已经有匹配点),只接跳过 
            //这种情况不会增加匹配数 
            if(getf(u)==getf(v)||vis[v]==2) continue;
            //如果没有被染色 
            if(!vis[v])
            {
                //先染为白色,将前继点指向u 
                vis[v]=2;
                pre[v]=u;
                //如果没有被匹配过,直接匹配成功 
                if(!match[v])
                {
                    //增广路取反 
                    for(int x=v,last;x;x=last)
                        last=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;        
                    return 1;
                }
                //如果被匹配过,则把匹配v的点染为黑色,放入队列中    
                vis[match[v]]=1;
                q.push(match[v]);
            }
            //v是黑色,形成奇环,则缩点(开花)。 
            else 
            {
                int lca=LCA(u,v);
                blossom(u,v,lca);
                blossom(v,u,lca);
            }
        }    
    }
    return 0;
}
int main(void)
{
    while(~scanf("%d%d",&n,&m))    
    {
        Init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            add(u,v),add(v,u);    
        }        
        for(int i=1;i<=n;i++)
            if(!match[i]&&aug(i))
                ans++;    
        printf("%d\n",ans);
        for(int i=1;i<=n;i++)
            printf("%d%c",match[i]," \n"[i==n]);
    }
    return 0;    
}