一、例题:
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;
}