帶花樹——一般圖最大匹配
問題
給定一個圖,求該圖的最大匹配。即找到最多的邊,使得每個點至多屬於一條邊。
這個問題的退化版本就是二分圖最大匹配。
由於二分圖中不存在奇環,偶環對最大匹配並無影響(可以調整)。所以增廣路算法是可以順利應用的。
在一般圖中,我們還是嘗試使用BFS增廣路的算法。
然而一般圖中還會出現奇環,在尋找增廣路的時候,怎麽處理奇環上的沖突?
目的就是將奇環不斷地縮起來(縮花),使得整個圖在使用增廣算法的時候不受影響,即不會經過奇環。
?
?
?
花
? 一朵花由一個奇環縮點而成,一朵花裏面可能還會有花。
設這個奇環共有\(2k+1\)個點,那麽在環內至多可以匹配到\(k\)
但是,形象地說,這個點可以在環裏面自由移動。
在圖上將每個奇環縮成一個點成為一朵花,其實和原圖是等價的,為什麽?
因為如果有合法增廣路經過這朵花,在交替匹配邊的時候,這朵花一定能通過那個自由點適應變化。
畫個圖就明白了。
使用並查集維護花,所有點的代表元指向這朵花裏面在這次增廣時BFS樹中深度最淺的點。
?
?
實現
從每個未匹配的點開始進行BFS,找到一條合法增廣路徑以後,增廣並退出。
記這個未匹配的點為0類點,之後的點按10交替標序。
每次在一個0點,枚舉下一個點:
1. 如果下一個點沒有匹配,那麽就找到了一個增廣路,回溯並增廣。
2. 如果下一個點有匹配,那麽就把它的匹配點加入隊列中。
?
設在搜索過程中,搜到連成環的邊是\((u,v)\)。
如果連成偶環,不需要理會;如果連城奇環,並且\(u\)和\(v\)不在一朵花內,就要對整個奇環縮花了。
搜到奇環的時候,由於每次從0點枚舉下一個點,\(u\)和\(v\)都是0點,環一定是這樣的:
首先要求出\(u\)和\(v\)的花意義下的\(lca\),它也是\(0\)點。做法是不斷暴力向上跳,實際上是兩個兩個地跳。
偽代碼如下:
int getlca(int x,int y){
clear visit[];
x=find(x); y=find(y);
while (1){
if(x){
if(x has been visited) return x;
visit[x]=1;
x=find(pre[match[x]])
}
swap(x,y);
}
}
其中\(match[x]\)記錄的是\(x\)的匹配點,而\(pre\)記錄的是每個1點的BFS父親,\(find(x)\)返回\(x\)所屬花的代表元。
廣義的講,\(pre[x]\)的定義是如果\(x\)點失去了當前匹配點,那麽它應該匹配誰。
然後,對整個環縮花,從\((u,v)\)這條邊向兩邊叠代。由於兩邊情況相同,一個函數調用兩次即可:
int lca=getlca(u,v);
blossom(u,v,lca);
blossom(v,u,lca);
? 首先是\(x\)和\(y\)的\(pre\)要互連,其次是把兩個點的並查集的父親設為\(lca\)(如果它是並查集的代表元,不是的話待會會遍歷到的)。
最後要將環中的1點全部扔進隊列裏,因為整個環縮起來了以後成為了一個點,要繼續作為一個點尋找增廣路,等價的做法就是把花裏的所有點扔進隊列(此時0點已經進過隊列了所以不用扔);而縮起來的花是一個0點,故要將所有點的標號設為0(把1點設為0點就好)。
代碼中,用\(s[]\)記錄標號。
void blossom(int x,int y,int lca){
while(find(x)!=lca){
pre[x]=y;
if(s[match[x]]==1){
s[match[x]]=0;
q.push(match[x]);
}
if(fa[x]==x) fa[x]=lca;
if(fa[match[x]]==match[x]) fa[match[x]]=lca;
y=match[x];
x=pre[y];
}
}
?
?
?
完整代碼如下
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int N=510,M=125000;
int n,m;
int h[N],tot;
int match[N],s[N],pre[N],vis[N],tim;
int fa[N];
queue<int> q;
struct Edge{int v,next;}g[M*2];
inline void addEdge(int u,int v){
g[++tot].v=v; g[tot].next=h[u]; h[u]=tot;
g[++tot].v=u; g[tot].next=h[v]; h[v]=tot;
}
inline int find(int x){return fa[x]==x?x:(fa[x]=find(fa[x]));}
int getlca(int x,int y){
tim++;
x=find(x); y=find(y);
for(;;x^=y^=x^=y)
if(x){
if(vis[x]==tim) return x;
vis[x]=tim;
x=find(pre[match[x]]);
}
}
void blossom(int x,int y,int lca){
while(find(x)!=lca){
pre[x]=y;
if(s[match[x]]==1){
s[match[x]]=0;
q.push(match[x]);
}
if(fa[x]==x) fa[x]=lca;
if(fa[match[x]]==match[x]) fa[match[x]]=lca;
y=match[x];
x=pre[y];
}
}
int solve(int x){
for(int i=1;i<=n;i++) fa[i]=i;
memset(s,-1,sizeof s);
memset(pre,0,sizeof pre);
while(!q.empty()) q.pop();
s[x]=0;
q.push(x);
while(!q.empty()){
int u=q.front(); q.pop();
for(int i=h[u],v;i;i=g[i].next){
v=g[i].v;
if(s[v]==-1){
pre[v]=u;
s[v]=1;
if(!match[v]){
for(int go=1;go;v=go,u=pre[go]){
go=match[u];
match[u]=v; match[v]=u;
}
return 1;
}
s[match[v]]=0;
q.push(match[v]);
}
else if(!s[v]&&find(u)!=find(v)){
int lca=getlca(u,v);
blossom(u,v,lca);
blossom(v,u,lca);
}
}
}
return 0;
}
int main(){
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
addEdge(u,v);
}
int ans=0;
for(int i=1;i<=n;i++)
if(!match[i])
ans+=solve(i);
printf("%d\n",ans);
for(int i=1;i<=n;i++) printf("%d ",match[i]);
return 0;
}
帶花樹——一般圖最大匹配