强烈推荐2015年集训队论文中陈胤伯的《浅谈图的匹配算法及其应用》,里面对匹配的性质及各类匹配算法都有很清晰的讲解。
以下的内容只是读完论文后我的一些体会。
求最大匹配的过程就是在求一条从未匹配点出发走到另一个未匹配点,并且路径是匹配边与非匹配边交替的简单路径。在二分图中,根据dfs/bfs的过程,我们可以画出一棵交错树,树中的边匹配边与非匹配边交替:
容易发现树中的白点都是被匹配边“拉入”树中的,我们只需要dfs白点的出边。
我们将交错树中的点分为1类点和2类点,其中根为1类点,每次我们枚举的是1类点的出边。
在二分图中只存在偶环,所以当一个点 u u u dfs到一个已经访问过的点 v v v 时, v v v必定是2类点,此时再次dfs v v v 显然并无意义。
但在一般图中,当一个点
u
u
u dfs到一个已经访问过的点
v
v
v 时,
v
v
v可能是1类点,这就造成了环上的2类点的出边也可能出现在交错路中,如下图:
不难发现,沿匹配边进入
h
h
h可以到环上其他任意点沿非匹配边出去(顺时针,逆时针总有一种走法可行)
于是我们可以将这个奇环缩为一个点(非匹配边连成一朵花), h h h为花根。并将1类点的 p r e pre pre设为它在环上经过非匹配边到达的那个点。
截取论文中的最后一段:
论文中前面提到如果在某一次找不到从
x
x
x出发的增广路,增广多次以后仍然没有从
x
x
x出发的增广路,所以每个点只需要找一遍,总复杂度
O
(
n
∗
(
n
2
+
m
)
)
=
O
(
n
3
)
O(n*(n^2+m))=O(n^3)
O(n∗(n2+m))=O(n3)
PS:uoj79可以用随机化水过,事实上最大匹配问题似乎随机化的正确性表现的异常优秀 。
Code(uoj79):
#include<bits/stdc++.h>
#define maxn 505
#define maxm 250005
using namespace std;
int n,m;
int fir[maxn],nxt[maxm],to[maxm],tot;
inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
int mat[maxn],typ[maxn],F[maxn],pre[maxn],q[maxn],ql,qr,vis[maxn],tim;
int find(int x){return !F[x]?x:F[x]=find(F[x]);}
int LCA(int u,int v){//O((u,v)在BFS树上的距离)
for(++tim;;swap(u,v)) if(u){
if(vis[u=find(u)]==tim) return u;//点要对应到花根上
vis[u]=tim,u=pre[mat[u]];//花根连出的环的两条边一定是非匹配边
}
}
void blossom(int u,int v,int tp){
while(find(u)!=tp){//缩花可能是几个小花并成一个大花,此时小花就没有必要存了,保留大花。
//小花花根的pre需要指向从u走上来的点,所以这里不能令u=find(u)
pre[u]=v,v=mat[u];
if(typ[v]==2) typ[q[++qr]=v]=1;
if(find(u)==u) F[u]=tp;
if(find(v)==v) F[v]=tp;//前面的判断貌似可以不要?
u=pre[v];
}
}
bool aug(int s){
for(int i=1;i<=n;i++) typ[i]=F[i]=pre[i]=0;
typ[q[ql=qr=1]=s]=1;
while(ql<=qr){
int u=q[ql++];
for(int i=fir[u],v;i;i=nxt[i])
if(typ[v=to[i]]!=2&&find(u)!=find(v)){//找到的点要么没访问过,要么就是找到新的可以倒着走的环
if(!typ[v]){
typ[v]=2,pre[v]=u;
if(mat[v]) typ[q[++qr]=mat[v]]=1;
else{
while(v) {int t=mat[pre[v]]; mat[mat[v]=pre[v]]=v,v=t;}
return 1;
}
}
else {int lca=LCA(u,v); blossom(u,v,lca),blossom(v,u,lca);}
}
}
return 0;
}
int main()
{
scanf("%d%d",&n,&m); int x,y;
while(m--) scanf("%d%d",&x,&y),line(x,y),line(y,x);
int ans=0;
for(int i=1;i<=n;i++) if(!mat[i]) ans+=aug(i);
printf("%d\n",ans);
for(int i=1;i<=n;i++) printf("%d%c",mat[i],i==n?10:32);
}
UOJ的一个随机化的提交。
随机的时候注意点的顺序要 random_shuffle 不然会被卡。