luogu P6113 【模板】一般图最大匹配(带花树)

整理的算法模板合集: ACM模板


在这里插入图片描述
总结一下带花树算法的流程
1.每次找一个未匹配的点出来增广
2.在增广过程中,如果相邻点是白点,或者是同一朵花中的节点,则直接跳过这个点
3.如果相邻点是一个未被匹配过的白点,证明找到了增广路,沿着原有的pre和match路径,对这一次的匹配结果进行更新
4.如果相邻点是一个被匹配过的白点,那么把这个点的匹配点丢进队列中,尝试能否让这个点的匹配点找到另外一个点进行匹配,从而可以增广。
(以上步骤同匈牙利算法)
5.如果相邻点是一个被匹配过的黑点,证明此时出现了奇环,我们需要将这个环缩成一个黑点。具体的实现过程是:找到他们的最近花公共祖先,也就是他们的花根,同时,沿着当前这两个点一路到花根,将花上的所有节点全部染成黑点(因为一朵花都是黑点),将原来的白点丢进栈中。同时,修改花上所有点的pre,此时,只剩下花根并不与花内的节点相匹配。

这里找到的也是一个增广路,也就是非匹配边和匹配边交替而成的,所以我们往上找他们两个点的lca的时候,就是从x开始往上走一步走到他的匹配点y,然后再走一步走到y的前驱z,这样一步一步地往上走到他们的lca即可。(不断的向上走交替地沿着match和pre边向上)

两个点u和v一次走一个,每走一步,如果另一个点还有的话就交换到另一个点往上走一步一步,交替地往上走。

这样暴力跑是 O ( n ) O(n) O(n)的,但是带花树的总的时间复杂度为 O ( n 3 ) O(n^3) O(n3),所以这里无所谓慢一点。(或者说我们只能这样走)…

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>

using namespace std;
const int N = 5007, M = 500007, INF = 0x3f3f3f3f;

int n, m;
int dfn[M];
int low[N], fa[N];
int tim;
int pre[N];//前驱
int match[N];//匹配点
queue<int>q;
int head[N], ver[M], nex[M], tot;
int vis[N];

int ans;

void add(int x, int y)
{
    ver[tot] = y;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

int Find(int x)
{
    if(fa[x] == x)return x;
    return fa[x] = Find(fa[x]);
}



//朴素的lca,根据建图的方式暴力跳
inline int lca(int x, int y)
{
    ++ tim;//时间戳 / 缩点的编号
    while(dfn[x] != tim){
        dfn[x] = tim;//每走一步就标记一下, 如果遇见一个点已经被标记过了就说明这个点已经被另一个点走过了,也就说明是lca
        x = Find(pre[match[x]]);
        if(y)swap(x, y);
    }
    return x;//lca
}
//开花(缩环)
//x和y是要开花的两个点(此时x和y都是黑点刚找到奇环的时候)
//w是他们的lca
//整个花缩完之后就是一个黑点

//!这里虽然是两个点但是只是从x走到了x和y的lca:w,y到w路径上的点并没有染色或者丢到队列里去,所以要开两次,一次x一次y。
inline void blossom(int x, int y, int w)
{
    while(Find(x) != w){
        pre[x] = y;
        y = match[x];
        //如果是白点就直接染成黑色,然后丢到队列里面
        vis[y] = 1, q.push(y);
        if(Find(x) == x)fa[x] = w;
        if(Find(y) == y)fa[y] = w;
        x = pre[y];
    }
}

inline bool aug(int s)
{
    if((ans + 1) * 2 > n)return 0;

    for(int i = 1; i <= n; ++ i)
        fa[i] = i, pre[i] = vis[i] = 0;
    while(!q.empty())q.pop();
    q.push(s);
    vis[s] = 1;//从黑点开始
    //队列中都是黑点,所以遇到相邻未染色的点都染成白点
    while(!q.empty()){
        int x = q.front();
        q.pop();
        for(int i = head[x] ;~i; i = nex[i]){
            int y = ver[i];
            //如果相邻点是白点,或者是同一朵花中的节点,则直接跳过这个点
            if(Find(x) == Find(y) || vis[y] == 2) continue;
            if(!vis[y]){//未染色(匹配)点,说明找到了增广路
                vis[y] = 2;//没染色就把它染成白色
                pre[y] = x;
                if(!match[y]){
                    int u = y;
                    while(u){
                        int v = pre[u], z = match[v];
                        match[u] = v;match[v] = u;
                        u = z;
                    }
                    return 1;
                }
                vis[match[y]] = 1, q.push(match[y]);
            }
            else {
                int w = lca(x, y);
                blossom(x, y, w);
                blossom(y, x, w);
            }
        }
    }
    return 0;
}

int main()
{
    memset(head, -1, sizeof head);

    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; ++ i){
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y), add(y, x);
    }

    for(int i = 1; i <= n; ++ i){
        if(!match[i])
            ans += aug(i);
    }

    printf("%d\n", ans);

    for(int i = 1; i <= n; ++ i)
        printf("%d%s", match[i], i == n ? "\n" : " ");
    return 0;
}
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页