UOJ #79. 一般图最大匹配 带花树模板

86 篇文章 0 订阅

题目链接:http://uoj.ac/problem/79

 

中文题面不解释,为了留一个板子写的,尽量在理解了,不是很容易啊。。这个算法很是精妙,贴几个链接方便复习。。不经常碰到总是会忘记的。

看了其他大佬的博客一点点照着习惯改的。。还写了点备注什么的。。看不懂戳下面。。

https://blog.csdn.net/qq_36797743/article/details/60968291

https://www.cnblogs.com/zhsl/p/3271754.html


#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
using namespace std;
typedef long long ll;
const int maxm=124752*2;
const int maxn=505*2;
int to[maxm],nex[maxm],head[maxn],cnt;
int n,m,fa[maxn];
int link[maxn];//表示最终匹配了哪个点
int match[maxn];//寻找增广路用的父亲
int id[maxn];
//标示这个点的类型 -1未访问 0为S点 1为T点
int road[maxn]; //记录当前的一整条增广路
int tmplink[maxn]; //存储暂时相连的点
int ti; //寻找lca时打的标记
int vis[maxn];//找lca时判断是否为同一次查找
void add(int u,int v){
    to[cnt]=v,nex[cnt]=head[u];
    head[u]=cnt++;
}
int fin(int x){
    return fa[x]==x?x:fa[x]=fin(fa[x]);
}
int lca(int x,int y){  //去找lca
    //这里的lca的意思是 S点找到S点
    //肯定是在第二次找S点的时候找到了一个也在这个点开始搜到的点
    //如1->2 1->3 那么2在找到3的时候是从1延伸出来的 lca就是这个点1
    ti++;
    while(vis[x]!=ti){
        if(x!=0){
            x=fin(x); //并查集找到这个花的根
            //一个奇环被缩成一个点之后 就只当成一朵花
            //这个点就是这朵花的根
            if(vis[x]==ti) return x; //如果就是这次寻找的
            vis[x]=ti;
            if(link[x]!=0) x=fin(tmplink[link[x]]);
            //如果x有连接 那么就去找这个点原来连接上的点的暂时存储的点
            //即不断地向上爬 去找它的根
            else x=0;
            //否则换个点进行寻找
        }
        swap(x,y);
    }
    return x;
}
int st,ed;
//对环进行特殊化处理 表示点x和点y相撞 并且是从k衍生的
void Change(int x,int y,int k){
    while(fin(x)!=k){
        tmplink[x]=y;
        int z=link[x];
        id[z]=0; road[ed++]=z; if(ed>=maxn-1) ed=1;
        if(fin(z)==z) fa[z]=k;
        if(fin(x)==x) fa[x]=k;
        y=z; x=tmplink[y];
    }

}

//开始对点u进行增广
void deal(int u){
    for(int i=1;i<=n;i++){
        fa[i]=i; id[i]=-1;
    }
    st=1,ed=2;// 增广路的开始和结束
    road[st]=u; id[u]=0;

    while(st!=ed){
        int x=road[st];
        for(int i=head[x];~i;i=nex[i]){
            int v=to[i];
            if(!link[v]&&v!=u){
                //如果v没有被匹配,那么就把这条路上的点全部反一反进行标记
                tmplink[v]=x;
                int last,now=v,tmp;
                while(now){
                    //让tmp为now暂时存的那个点
                    //last是原来tmp的匹配点
                    //要把tmp和now连接,并把now变成last进行重复操作
                    //总的来说就是now强抢了tmp tmp原来匹配的点没办法只能去找其他点
                    tmp=tmplink[now];
                    last=link[tmp];
                    link[tmp]=now; link[now]=tmp;
                    now=last;
                }
                return ;
            }
            if(id[v]==-1){  //该点还未被访问过 尝试进行增广
                id[v]=1;
                tmplink[v]=x;
                id[link[v]]=0;  road[ed++]=link[v];
                if(ed>=maxn-1) ed=1;
            }
            else if(id[v]==0&&fin(x)!=fin(v)){
                int f=lca(x,v);
                Change(x,v,f);
                Change(v,x,f);
            }
        }
        st++;
        if(st>=maxn-1) st-1;
    }
}
int main(){
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    rep(i,1,m){
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
    }
    int ans=0;
    rep(i,1,n)
        if(!link[i])
            deal(i);

    rep(i,1,n) if(link[i]) ans++;

    printf("%d\n",ans/2);
    rep(i,1,n){
        printf("%d%c",link[i],i==n?'\n':' ');
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值