1. 程式人生 > >帶花樹演算法--一般圖最大匹配

帶花樹演算法--一般圖最大匹配

可以在uoj#79測試模板題
如標題,簡單地介紹一下帶花樹演算法,提供一個自認為不錯的模板
安利一篇介紹得很詳細的blog
由於本人實在太蒻。。。這篇blog就不講述任何關於演算法正確性的證明吧
(也許以後會有興趣翻譯原論文:EfficientAlgorithmsforFindingMaximalMatchinginGraphs,不過現在肯定是懶了。。)

可能有些選手會想苟蒻我一樣懶,二分圖匹配只會用Dinic做法,建議學習帶花樹之前看一下匈牙利演算法
就像匈牙利演算法一樣,帶花樹演算法的核心也是類似地每次找增廣路
假設對於原圖的k個點,已經找到了一些匹配,先把這些匹配留著
選擇一個尚未匹配的點s,作為起點,進行bfs,保留bfs樹
每次遍歷到一個點,如果它也沒匹配,那非常棒!因為這樣就找到了一條增廣路,直接增廣即可。
否則,記這個點為u,記它的配偶為v
對點u,標記為T節點,對於v,標記為S節點,並把v入隊,繼續bfs
(帶上起點s,顯然,每時每刻在佇列中的點都是S類節點)
那麼得到的bfs樹畫出來大概就是這樣的
這裡寫圖片描述

其中紅色節點為起點
顯然,有的時刻,從某個點出發,路徑上下一個點是被訪問過的,哪怎麼辦?
對這個節點分類討論
1.這個點是T節點
這裡寫圖片描述
就像圖中藍線連線的邊,顯然,此時找到了一個偶環,忽略即可

2.這個點是S節點
這裡寫圖片描述
還是圖中的藍線,此時就找到了一個長度為奇數的環
不過這時候的情況並不簡單。。。顯然,找不到一個方案構造增廣路
但是,如果這個環上存在任意一個點,使得這個點連線的點中存在一個未匹配點
那麼顯然,此時可以構造出一條一路回溯的增廣路
那麼,這個時候這個奇環上任意一個點有邊連線未匹配點都是等價的
於是,可以將這個奇環縮成一個新點,然後再繼續進行bfs,直到找到增廣路為止(或是證明不存在)

至此,帶花樹演算法求解一般圖匹配的基本步驟就大致清晰了
bfs->縮環->bfs->縮環->…->縮環->bfs->找到增廣路
那麼找到增廣路時,就是沿路將匹配情況取反,以及,我們需要把沿途經過的環(帶花樹的花)一個個展開。。。???
完全沒有寫的慾望??不知程式碼從何下手???
這邊提供一個本人認為較容易實現的板子。。(反正這個演算法都是靠板子?)

首先,對於每次選擇的bfs起點,1~n for一遍就行了
每次判斷當前點是否已配對,如果沒有,才進行bfs
對於每個遍歷到的T類點,記錄pre[i]為找到它的S類點編號
每次出現S找到S的情況就需要進行縮環
對於縮出來的環,只找一個環上的S點作為它的代表點
如果一朵花被包含在另一朵花內,那麼這朵花就不再表示出來了
其實我們不需要真正地去做這個縮環的過程,
只需要對每朵花內的pre陣列進行適當修改就行了
大概如圖所示:
這裡寫圖片描述


這是一個S找到S的情形
只需要大力將每個S的pre邊連上就行了
顯然,如果一個點找到了合法的匹配物件(即一個未匹配點)
我們可以很輕鬆的通過pre邊把需要修改的環修改掉
具體操作不妨參考本人程式碼中的函式
Link為縮環函式
Rebuild為上述需要更改的pre邊
Augment則為增廣函式

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
using namespace std;

const int N = 505;

int n,m,cnt,Num[105][7],fa[N],pre[N],Mark[N],mat[N],vis[N];

queue <int> Q;
vector <int> v[N];

inline int Getfa(int x)
{
    return x == fa[x] ? x : fa[x] = Getfa(fa[x]);
}

inline int getint()
{
    char ch = getchar(); int ret = 0;
    while (ch < '0' || '9' < ch) ch = getchar();
    while ('0' <= ch && ch <= '9')
        ret = ret * 10 + ch - '0',ch = getchar();
    return ret;
}

void Build()
{
    n = getint(); m = getint();
    while (m--)
    {
        int x = getint(),y = getint();
        v[x].push_back(y); v[y].push_back(x);
    }
}

inline void Augment(int p)
{
    while (p != -1)
    {
        int tmp = mat[pre[p]]; mat[p] = pre[p];
        mat[pre[p]] = p; p = tmp;
    }
}

inline void Rebuild(int x,int y,int lca)
{
    while (Getfa(x) != lca)
    {
        pre[x] = y;
        if (fa[x] == x) fa[x] = lca;
        if (fa[mat[x]] == mat[x]) fa[mat[x]] = lca;
        if (Mark[mat[x]] == 1) {Q.push(mat[x]); Mark[mat[x]] = 0;}
        y = mat[x]; x = pre[y];
    }
}

inline void Link(int x,int y)
{
    ++cnt; int p = Getfa(x);
    for (;;)
    {
        vis[p] = cnt; p = mat[p];
        if (p == -1) break; p = Getfa(pre[p]);
    }
    int lca; p = Getfa(y);
    for (;; p = Getfa(pre[mat[p]]))
        if (vis[p] == cnt) {lca = p; break;}
    Rebuild(x,y,lca); Rebuild(y,x,lca);
}

inline int BFS(int s)
{
    for (int i = 1; i <= n; i++)
        fa[i] = i,Mark[i] = pre[i] = -1;
    while (!Q.empty()) Q.pop(); Mark[s] = 0; Q.push(s);
    while (!Q.empty())
    {
        int k = Q.front(); Q.pop();
        for (int i = 0; i < v[k].size(); i++)
        {
            int to = v[k][i];
            if (Getfa(to) == Getfa(k)) continue;
            if (Mark[to] == -1)
            {
                Mark[to] = 1; pre[to] = k;
                if (mat[to] == -1)
                {
                    Augment(to); return 1;
                }
                Mark[mat[to]] = 0; Q.push(mat[to]);
            }
            else if (Mark[to] == 0) Link(to,k);
        }
    }
    return 0;
}

int main()
{
    #ifdef DMC
        freopen("DMC.txt","r",stdin);
    #endif

    int Ans = 0; Build();
    for (int i = 1; i <= n; i++) mat[i] = -1;
    for (int i = 1; i <= n; i++)
        if (mat[i] == -1) Ans += BFS(i);
    cout << Ans << endl;
    for (int i = 1; i <= n; i++)
        printf("%d%c",mat[i] == -1 ? 0 : mat[i],i == n ? '\n' : ' ');
    return 0;
}