問題
對於一個圖\(G(V,E)\),它的匹配\(M\)是二元組\((u,v)\)組成的集合,其中\(u,v\in V,(u,v)\in E\),並且\(M\)中不存在重復的點。
當\(|M|\)最大的時候,我們稱\(M\)為\(G\)的最大匹配。
當\(G\)是一個二分圖的時候,它的最大匹配可以用經典的匈牙利算法或網絡流算法求解。然而當\(G\)是一個一般的圖時,直接進行增廣就變得不可行了,例如下面這個例子(論文中的圖):
這個問題出現的原因,就是一個一般圖中會含有奇環,即一個點數為\(2k+1,k>0\)的環,而如果經過一個奇環,那么會得到兩條含有同一個點的匹配邊,這其實是不符合定義的。那為什么二分圖可以直接增廣呢?因為二分圖中不可能含有奇環,它所有的環都是偶環。因此,在一般圖匹配問題中,我們需要一種改進算法來解決奇環的問題。
算法
基本算法依然是分為\(n\)個階段尋找增廣路。問題主要在奇環上,那么我們分析一下這個奇環的性質。首先,奇環中有\(2k+1\)個點,所以最多有\(k\)組匹配。這就是說,有一個點沒有匹配,即這個點在環內兩邊的連邊都不是匹配邊,也只有這個點可以向環外連邊。
發現了這個性質,我們可以把整個奇環縮成一個點。縮完點后的圖如果可以找到一條增廣路,那么原圖中也可以找到一條增廣路,因為如果增廣路經過奇環那么奇環內的增廣路可以還原出來。
這就是帶花樹算法的思想。整個求解過程分為\(n\)個階段,每個階段從沒有匹配的\(s\)點開始bfs找增廣路。搜索的開始,把\(s\)點加入隊列中,標記它為A類點。如果從\(x\)點出發,搜索到了一個未標記的點,有兩種情況。如果這個未標記點有匹配,那么把這個點設為\(B\)類點,它的匹配點設為\(A\)類點,加入隊列繼續增廣。如果這個點沒有匹配,又因為我們是從一個未匹配點開始進行搜索的,所以這說明我們找到了一條增廣路,沿着過來的邊找回去,展開帶花樹,修改搜索的過程中,如果我們遇到了偶環,那么不管它,因為它不會影響求解。如果遇到了一個奇環,那么我們找到當前點\(x\)和找到的點\(v\),求出他們的最近公共花祖先,然后把環縮掉。這里我們用並查集實現。
我們在縮環的時候,處理出一個\(pre\)數組,表示我們回跳的時候走到這里該往哪一個方向走回去。回跳的時候,每次找到pre,然后修改這條邊,接着跳到pre原來的match處。如果我們倒着進入一個花的時候,上方的邊為非匹配邊,那么我們會往下走,這個時候pre就應該往下設。中間相遇的位置pre互相連接,pre[x]=y,pre[y]=x
。
算法分為\(n\)個階段,每個階段最多把整個圖遍歷一次,每個點會最多被縮\(n\)次花,所以總復雜度為\(O(n^3)\)。
代碼
uoj79,一般圖匹配模板題。
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const int maxn=505;
const int maxm=maxn*maxn*2;
int n,m,que[maxm],ql,qr,pre[maxn],tim=0;
struct edge {
int v,nxt;
} e[maxm];
int h[maxn],tot=0;
int match[maxn],f[maxn],tp[maxn],tic[maxn];
int find(int x) {
return f[x]==x?f[x]:f[x]=find(f[x]);
}
void add(int u,int v) {
e[++tot]=(edge){v,h[u]};
h[u]=tot;
}
int lca(int x,int y) {
for (++tim;;swap(x,y)) if (x) {
x=find(x);
if (tic[x]==tim) return x; else tic[x]=tim,x=pre[match[x]];
}
}
void shrink(int x,int y,int p) {
while (find(x)!=p) {
pre[x]=y,y=match[x];
if (tp[y]==2) tp[y]=1,que[++qr]=y;
if (find(x)==x) f[x]=p;
if (find(y)==y) f[y]=p;
x=pre[y];
}
}
bool aug(int s) {
for (int i=1;i<=n;++i) f[i]=i;
memset(tp,0,sizeof tp),memset(pre,0,sizeof pre);
tp[que[ql=qr=1]=s]=1; // 1: type A ; 2: type B
int t=0;
while (ql<=qr) {
int x=que[ql++];
for (int i=h[x],v=e[i].v;i;i=e[i].nxt,v=e[i].v) {
if (find(v)==find(x) || tp[v]==2) continue;
if (!tp[v]) {
tp[v]=2,pre[v]=x;
if (!match[v]) {
for (int now=v,last,tmp;now;now=last) {
last=match[tmp=pre[now]];
match[now]=tmp,match[tmp]=now;
}
return true;
}
tp[match[v]]=1,que[++qr]=match[v];
} else if (tp[v]==1) {
int l=lca(x,v);
shrink(x,v,l);
shrink(v,x,l);
}
}
}
return false;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
freopen("my.out","w",stdout);
#endif
n=read(),m=read();
for (int i=1;i<=m;++i) {
int x=read(),y=read();
add(x,y),add(y,x);
}
int ans=0;
for (int i=1;i<=n;++i) ans+=(!match[i] && aug(i));
printf("%d\n",ans);
for (int i=1;i<=n;++i) printf("%d ",match[i]);
puts("");
return 0;
}