1. 程式人生 > >帶花樹——一般圖最大匹配

帶花樹——一般圖最大匹配

HA 函數 ear cstring tro 互連 code 全部 ()

問題

  給定一個圖,求該圖的最大匹配。即找到最多的邊,使得每個點至多屬於一條邊。

  這個問題的退化版本就是二分圖最大匹配。

  由於二分圖中不存在奇環,偶環對最大匹配並無影響(可以調整)。所以增廣路算法是可以順利應用的。

  在一般圖中,我們還是嘗試使用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;
}

帶花樹——一般圖最大匹配