P6113 一般图最大匹配

题目链接

带花树模板代码:

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 1e3+5;
const int maxm = 4e5+5;

struct Edge{
    int next,to;
}edge[maxm];

int head[maxn],tot=1;
int vis[maxn],pre[maxn],match[maxn],f[maxn],Id,id[maxn];
//vis[i]: 0(未染色) 1(黑色) 2(白色)
//match[i]: i的匹配点
//f[i]: i在带花树中的祖先
//pre[i]: i的非匹配边的另一点 
//id: 找LCA用 
int n,m,ans;
// queue<int> q;

void add(int u,int v){
    edge[++tot].to=v;edge[tot].next=head[u];
    head[u]=tot;
    edge[++tot].to=u;edge[tot].next=head[v];
    head[v]=tot;
}

int find(int x){
    return x==f[x]?x:f[x]=find(f[x]);
}

int Lca(int x,int y){//查询x和y在带花树中的LCA
    for(++Id;;swap(x,y)){//沿着增广路向上找lca 
        if(x){
            x=find(x);//有可能环中有环(花中有花),所以用并查集找祖先,只处理祖先节点
            if(id[x]==Id){//x,y在同一环中,一定会找到已被编号的点,该点即为LCA
                return x;
            }else{
                id[x]=Id,x=pre[match[x]];//给点编号,并沿着非匹配边向上找
            }
        }
    }
}

void blossom(int x,int y,int l,queue<int> &q){//缩点(开花),将x、y到LCA(l)路径中的点,缩为一点
    while(find(x)!=l){
        pre[x]=y,y=match[x];//增广路取反
        if(vis[y]==2){//如果x、y的奇环中有白点,将其染为黑点,放入队列,让其去找不是环中的匹配点	
            vis[y]=1,q.push(y);
        }
        if(find(x)==x){//只改变是根的点 
            f[x]=l;
        }
        if(find(y)==y){
            f[y]=l;
        }
        x=pre[y];//增广路取反 
    }
}

bool bfs(int s){//每次都以s为起点bfs,建带花树
    for(int i=1;i<=n;i++){
        vis[i]=pre[i]=0;f[i]=i;
    }
    queue<int> q;
    // while(!q.empty()){q.pop();}
    q.push(s);vis[s]=1;
    while(!q.empty()){
        int now=q.front();
        q.pop();
        for(int i=head[now];i;i=edge[i].next){
            int v=edge[i].to;
            if((find(v)==find(now))||vis[v]==2)continue;//如果已经在同一个环(花)中或者是白点(意为这已经有匹配点),直接跳过,这种情况不会增加匹配数 
            if(!vis[v]){//如果没有被染色
                vis[v]=2;pre[v]=now;//先染为白色,将前继点指向u 
                if(!match[v]){//如果没有被匹配过,直接匹配成功 
                    for(int x=v,last;x;x=last){//增广路取反 
                        last=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;
                    }
                    return true;
                }
                vis[match[v]]=1;//如果被匹配过,则把匹配v的点染为黑色,放入队列中
                q.push(match[v]);
            }else{//v是黑色,形成奇环,则缩点(开花)。
                int lca=Lca(now,v);
                blossom(now,v,lca,q),blossom(v,now,lca,q);
            }
        }
    }
    return false;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
    }
    for(int i=1;i<=n;i++){
        if(!match[i]&&bfs(i)){
            ans++;
        }
    }
    printf("%d\n",ans);
    for(int i=1;i<=n;i++){
        printf("%d%c",match[i],i==n?'\n':' ');  
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值