一、例题:
Universal Online Judge #79. 一般图最大匹配

从前一个和谐的班级,所有人都是搞OI的。有 n 个是男生,有 0 个是女生。男生编号分别为 1,…,n。

现在老师想把他们分成若干个两人小组写动态仙人掌,一个人负责搬砖另一个人负责吐槽。每个人至多属于一个小组。

有若干个这样的条件:第 v 个男生和第 u 个男生愿意组成小组。

请问这个班级里最多产生多少个小组?

输入格式
第一行两个正整数,n,m。保证 n≥2。

接下来 mm 行,每行两个整数 v,u 表示第 v 个男生和第 u 个男生愿意组成小组。保证 1≤v,u≤n,保证 v≠u,保证同一个条件不会出现两次。

输出格式
第一行一个整数,表示最多产生多少个小组。

接下来一行 n 个整数,描述一组最优方案。第 v 个整数表示 v 号男生所在小组的另一个男生的编号。如果 v 号男生没有小组请输出 0。

样例一
input
10 20
9 2
7 6
10 8
3 9
1 10
7 1
10 9
8 6
8 2
8 1
3 1
7 5
4 7
5 9
7 8
10 4
9 1
4 8
6 3
2 5

output
5
9 5 6 10 2 3 8 7 1 4

样例二
input
5 4
1 5
4 2
2 1
4 3

output
2
2 1 4 3 0

限制与约定
1≤n≤500,1≤m≤124750。

时间限制:1s
空间限制:256MB

①、网上大多数人的写法:

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<map>
#include<vector>
#define ll long long
#define llu unsigned ll
using namespace std;
const int maxn=510;
const int inf=0x3f3f3f3f;
int head[maxn],ver[maxn*maxn],nt[maxn*maxn];
int ha[maxn],match[maxn],pre[maxn],f[maxn],id[maxn];
//vis[i]: 0(未染色) 1(黑色) 2(白色)
//match[i]: i的匹配点
//f[i]: i在带花树中的祖先
//pre[i]: i的非匹配边的另一点
//id: 找LCA用
int n,m,tot,idx,ans;
queue<int>q;

void add(int x,int y)
{
    ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
}

int fi(int x)
{
    if(f[x]!=x)
        f[x]=fi(f[x]);
    return f[x];
}

void init(void)
{
    idx=ans=tot=0;
    memset(head,0,sizeof(head));
    memset(id,0,sizeof(id));
    memset(match,0,sizeof(match));
}

//查询x和y在带花树中的LCA
int LCA(int x,int y)
{
    //沿着增广路向上找lca ,x,y交替向上
    for(idx++,x=fi(x),y=fi(y);;swap(x,y))
    {
        //有可能环中有环(花中有花),所以用并查集找祖先,只处理祖先节点
        if(x)
        {
            if(id[x]==idx) return x;//x,y在同一环中,一定会找到已被编号的点,该点即为LCA。
            id[x]=idx,x=fi(pre[match[x]]);//给点编号,并沿着非匹配边向上找
        }
    }
}

//缩点(开花),将x、y到LCA(l)路径中的点,缩为一点
void blossom(int x,int y,int lca)
{
    while(fi(x)!=lca)
    {
        //增广路取反
        pre[x]=y;y=match[x];

        //如果x、y的奇环中有白点,将其染为黑点,放入队列,让其去找不是环中的匹配点
        if(ha[y]==2) ha[y]=1,q.push(y);

        //只改变是根的点
        if(fi(x)==x) f[x]=lca;
        if(fi(y)==y) f[y]=lca;
        x=pre[y];
    }
}

int bfs(int s)
{
    //每次都以s为起点bfs,建带花树

    for(int i=1;i<=n;i++)
        ha[i]=pre[i]=0,f[i]=i;
    while(q.size()) q.pop();
    q.push(s);
    ha[s]=1;

    while(q.size())
    {
        int x=q.front();
        q.pop();

        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i];

            //如果已经在同一个环(花)中或者是白点(意为这已经有匹配点),直接跳过
			//这种情况不会增加匹配数
            if(fi(x)==fi(y)||ha[y]==2) continue;

            //如果没有被染色
            if(!ha[y])
            {
                //先染为白色,将前继点指向x
                ha[y]=2;pre[y]=x;

                //如果没有被匹配过,直接匹配成功
                if(!match[y])
                {
                    //增广路取反
                    for(int k=y,last;k;k=last)
                        last=match[pre[k]],match[k]=pre[k],match[pre[k]]=k;
                    return 1;
                }
                //如果被匹配过,则把匹配v的点染为黑色,放入队列中
                ha[match[y]]=1,q.push(match[y]);
            }

            //v是黑色,形成奇环,则缩点(开花)
            else
            {
                int lca=LCA(x,y);
                blossom(x,y,lca),blossom(y,x,lca);
            }
        }
    }
    return 0;
}

int main(void)
{
    int x,y;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    for(int i=1;i<=n;i++)
    {
        if(!match[i])
            ans+=bfs(i);
    }

    printf("%d\n",ans);
    for(int i=1;i<=n;i++)
    {
        if(i!=1) putchar(' ');
        printf("%d",match[i]);
    }
    putchar('\n');
    return 0;
}

②、kuangbin 的板子
本质上是一样的,写法不同:

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<map>
#include<vector>
#define ll long long
#define llu unsigned ll
using namespace std;
const int maxn=510;
const int inf=0x3f3f3f3f;
int n,m;//n->点的个数
bool g[maxn][maxn],inq[maxn],inp[maxn],inb[maxn];
int match[maxn],q[maxn],f[maxn],ba[maxn];
int head,tail,s,t,nba;
int cnt;//匹配数,匹配对数是cnt/2

void cg(void)
{
    int x,y;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        g[x][y]=g[y][x]=true;
    }
}

void push(int x)
{
    q[++tail]=x;
    inq[x]=true;
}

int pop(void)
{
    return q[head++];
}

int find_common_ancestor(int x,int y)
{
    memset(inp,0,sizeof(inp));
    while(true)
    {
        x=ba[x];
        inp[x]=true;
        if(x==s) break;
        x=f[match[x]];
    }
    while(true)
    {
        y=ba[y];
        if(inp[y]) break;
        y=f[match[y]];
    }
    return y;
}

void reset_trace(int x)
{
    while(ba[x]!=nba)
    {
        int y=match[x];
        inb[ba[x]]=inb[ba[y]]=true;
        x=f[y];
        if(ba[x]!=nba) f[x]=y;
    }
}

void bloosom_contract(int x,int y)
{
    nba=find_common_ancestor(x,y);
    memset(inb,0,sizeof(inb));
    reset_trace(x);
    reset_trace(y);
    if(ba[x]!=nba) f[x]=y;
    if(ba[y]!=nba) f[y]=x;
    for(int k=1;k<=n;k++)
    {
        if(inb[ba[k]])
        {
            ba[k]=nba;
            if(!inq[k]) push(k);
        }
    }
}

void find_augmenting_path(void)
{
    memset(inq,false,sizeof(inq));
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++)
        ba[i]=i;
    head=1,tail=0;
    push(s);
    t=0;
    while(head<=tail)
    {
        int x=pop();
        for(int y=1;y<=n;y++)
        {
            if(g[x][y]&&(ba[x]!=ba[y])&&match[x]!=y)
            {
                if(y==s||match[y]>0&&f[match[y]]>0)
                    bloosom_contract(x,y);
                else if(f[y]==0)
                {
                    f[y]=x;
                    if(match[y]>0)
                        push(match[y]);
                    else
                    {
                        t=y;
                        return ;
                    }
                }
            }
        }
    }
}

void augment_path(void)
{
    int x,y,z;
    x=t;
    while(x>0)
    {
        y=f[x];
        z=match[y];
        match[y]=x;
        match[x]=y;
        x=z;
    }
}

void edmonds(void)
{
    memset(match,0,sizeof(match));
    for(int x=1;x<=n;x++)
    {
        if(match[x]==0)
        {
            s=x;
            find_augmenting_path();
            if(t>0) augment_path();
        }
    }
}

void print_match(void)
{
    cnt=0;
    for(int i=1;i<=n;i++)
    {
        if(match[i]>0) cnt++;
    }
    printf("%d\n",cnt/2);
    for(int i=1;i<=n;i++)
    {
        if(i!=1) putchar(' ');
        printf("%d",match[i]);
    }
    putchar('\n');
}

int main(void)
{
    cg();
    edmonds();
    print_match();
    return 0;
}