• 图论:一般图最大匹配(带花树)


    模板:直接求一般图最大匹配:

    同时求出match[i]表示与第i个节点匹配的点是哪一个

    点击查看折叠代码块
    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1010;
    const int inf=0x3f3f3f3f;
    typedef long long ll;
    
    int n,m;
    int mp[maxn][maxn];
    int f[maxn];//子图中每个点需要达到的度数
    int x[maxn],y[maxn];//原图的边对应的两点
    int deg[maxn];
    int fa[maxn];//并查集
    int pre[maxn],match[maxn];
    int st,ed,newfa,ans,ct=0;
    bool g[maxn][maxn],inque[maxn],inpath[maxn];//新图的邻接矩阵,是否在队列中,是否在增广路中
    bool inhua[maxn];//点是否在花内
    
    int head,tail;
    int que[maxn];
    
    void _push(int x){
        que[tail++]=x;
    }
    
    int _pop(){
        int x=que[head++];
        return x;
    }
    
    int lca(int u,int v){//朴素法找最近公共祖先
        memset(inpath,0,sizeof(inpath));
        while(1){
            u=fa[u];//u变成u的祖先
            inpath[u]=1;
            if(u==st) break;
            u=pre[match[u]];
        }
        while(1){
            v=fa[v];
            if(inpath[v]) break;
            v=pre[match[v]];
        }
        return v;
    }
    
    void reset(int u){//缩环
        int v;
        while(fa[u]!=newfa){
            v=match[u];
            inhua[fa[u]]=inhua[fa[v]]=1;
            u=pre[v];
            if(fa[u]!=newfa) pre[u]=v;
        }
    }
    
    void contract(int u,int v){
        newfa=lca(u,v);
        memset(inhua,0,sizeof(inhua));
        reset(u);
        reset(v);
        if(fa[u]!=newfa) pre[u]=v;
        if(fa[v]!=newfa) pre[v]=u;
        for (int i=1;i<=ct;i++){
            if(inhua[fa[i]]){
                fa[i]=newfa;
                if(!inque[i]){
                    inque[i] = 1;
                    _push(i);
                }
            }
        }
    }
    
    void aug(){
        int u,v,w;
        u=ed;
        while(u>0){
            v=pre[u];
            w=match[v];
            match[v]=u;
            match[u]=v;
            u=w;
        }
    }
    
    void findaug(){//找增广路
        memset(inque,0,sizeof(inque));
        memset(pre,0,sizeof(pre));
        for(int i=1;i<=ct;i++) fa[i]=i;//初始化并查集
    
        head=tail=1;
        _push(st);
        ed=0;
        while(head<tail){
            int u=_pop();
            for (int v=1;v<=ct;v++){
                if(g[u][v] && (fa[u]!=fa[v]) && match[u]!=v)//如果两个点之间有边,两个点父亲不同且两个点之间不是匹配
                {
                    if(v==st || (match[v]>0) && pre[match[v]]>0) //成环了
                        contract(u,v);
                    else if(pre[v] == 0){
                        pre[v]=u;
                        if(match[v]>0) _push(match[v]);
                        else{
                            ed=v;
                            return ;
                        }
                    }
                }
            }
        }
    }
    
    void edmonds(){//带花树,求匹配
        memset(match,0,sizeof(match));
        for (int u=1;u<=ct;u++){//对于新图中每个点
            if(match[u]==0){//如果该点未匹配过
                st=u;
                findaug();//以st开始寻找增广路
                if(ed>0) aug();//找到增广路,重新染色,反向
            }
        }
    }
    //以上是带花树求最大匹配算法,可以作为一个板子
    
    int main(){
        ct = 0;
        cin>>n>>m;
        for (int i=1;i<=m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            g[u][v] = g[v][u] = 1;
        }
        ct = n;//ct记录的是图点的总个数,根据题意可能需要拆点
        edmonds();
        ans = 0;
        for (int i=1;i<=ct;i++){
            if(match[i]!=0){
                // cout<<"i = "<<i<<" match = "<<match[i]<<endl;
                ans++;
            }
        }
        int Maxmatch = ans/2;
        cout<<Maxmatch<<endl;
        return 0;
    }
    
    /*
    6 6
    1 2
    2 3
    3 4
    4 5
    1 5
    4 6
    
    i = 1 match = 5
    i = 2 match = 3
    i = 3 match = 2
    i = 4 match = 6
    i = 5 match = 1
    i = 6 match = 4
    3
    */
    
    

    例题:hdu3551

    点击查看折叠代码块
    /*
    2020-7-14
    
    hdu-3551,子图点度数为任意
    
    一般图匹配_Edmond's Algorithm
    
    建图:将每个点根据f[i]拆成f[i]个点。
    将每一条边拆成两个点,将这条边关联的两个点的拆点分别与边的拆点相连,还有边的拆点也要相连:
    
    如 1-2 这条边,假设f[1]=1,f[2]=2,
    则我们将1号点拆成点1,2号点拆成点2和点3,1-2这条边拆成两个点4和点5,将点1与点4相连,点2和点3都与点5相连,点4与点5相连
    
    建图完成后,对这个图跑带花树找到匹配,如果这个匹配是完美匹配,则说明存在子图。
    参考:https://www.cnblogs.com/xiongtao/p/11189452.html
    */
    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1010;
    const int inf=0x3f3f3f3f;
    typedef long long ll;
    
    int n,m;
    int mp[maxn][maxn];
    int f[maxn];//子图中每个点需要达到的度数
    int x[maxn],y[maxn];//原图的边对应的两点
    int deg[maxn];
    int fa[maxn];//并查集
    int pre[maxn],match[maxn];
    int st,ed,newfa,ans,ct=0;
    bool g[maxn][maxn],inque[maxn],inpath[maxn];//新图的邻接矩阵,是否在队列中,是否在增广路中
    bool inhua[maxn];//点是否在花内
    
    int head,tail;
    int que[maxn];
    
    void _push(int x){
        que[tail++]=x;
    }
    
    int _pop(){
        int x=que[head++];
        return x;
    }
    
    int lca(int u,int v){//朴素法找最近公共祖先
        memset(inpath,0,sizeof(inpath));
        while(1){
            u=fa[u];//u变成u的祖先
            inpath[u]=1;
            if(u==st) break;
            u=pre[match[u]];
        }
        while(1){
            v=fa[v];
            if(inpath[v]) break;
            v=pre[match[v]];
        }
        return v;
    }
    
    void reset(int u){//缩环
        int v;
        while(fa[u]!=newfa){
            v=match[u];
            inhua[fa[u]]=inhua[fa[v]]=1;
            u=pre[v];
            if(fa[u]!=newfa) pre[u]=v;
        }
    }
    
    void contract(int u,int v){
        newfa=lca(u,v);
        memset(inhua,0,sizeof(inhua));
        reset(u);
        reset(v);
        if(fa[u]!=newfa) pre[u]=v;
        if(fa[v]!=newfa) pre[v]=u;
        for (int i=1;i<=ct;i++){
            if(inhua[fa[i]]){
                fa[i]=newfa;
                if(!inque[i]){
                    inque[i] = 1;
                    _push(i);
                }
            }
        }
    }
    
    void aug(){
        int u,v,w;
        u=ed;
        while(u>0){
            v=pre[u];
            w=match[v];
            match[v]=u;
            match[u]=v;
            u=w;
        }
    }
    
    void findaug(){//找增广路
        memset(inque,0,sizeof(inque));
        memset(pre,0,sizeof(pre));
        for(int i=1;i<=ct;i++) fa[i]=i;//初始化并查集
    
        head=tail=1;
        _push(st);
        ed=0;
        while(head<tail){
            int u=_pop();
            for (int v=1;v<=ct;v++){
                if(g[u][v] && (fa[u]!=fa[v]) && match[u]!=v)//如果两个点之间有边,两个点父亲不同且两个点之间不是匹配
                {
                    if(v==st || (match[v]>0) && pre[match[v]]>0) //成环了
                        contract(u,v);
                    else if(pre[v] == 0){
                        pre[v]=u;
                        if(match[v]>0) _push(match[v]);
                        else{
                            ed=v;
                            return ;
                        }
                    }
                }
            }
        }
    }
    
    void edmonds(){//带花树,求匹配
        memset(match,0,sizeof(match));
        for (int u=1;u<=ct;u++){//对于新图中每个点
            if(match[u]==0){//如果该点未匹配过
                st=u;
                findaug();//以st开始寻找增广路
                if(ed>0) aug();//找到增广路,重新染色,反向
            }
        }
    }
    //以上是带花树求最大匹配算法,可以作为一个板子
    
    
    void create(){//建图
        ct=0;
        memset(g,0,sizeof(g));
        for (int i=1;i<=n;i++){//对于每个点的度数进行拆点
            for (int j=1;j<=f[i];j++){
                mp[i][j] = ++ct;//原图中点i拆成f[i]个点的编号
            }
        }
    
        for (int i=1;i<=m;i++){//对于每条边拆成两个点,ct+1拆成x,ct+2拆成y
            for (int j=1;j<=f[x[i]];j++){//点拆点和边拆点相连,这条边的第一个点为x[i],f[x[i]]表示拆成的点的个数,mp[x[i]][j]表示x[i]这个点拆成的第j个点的编号
                g[ mp[x[i]][j] ][ct+1] = g[ct+1][ mp[x[i]][j] ]=1;
            }
            for (int j=1;j<=f[y[i]];j++){//同上
                g[ mp[y[i]][j] ][ct+2] = g[ct+2][ mp[y[i]][j] ]=1;
            }
            g[ct+1][ct+2]=g[ct+2][ct+1]=1;//边拆点相连
            ct+=2;
        }
    }
    
    void print(){
        ans=0;
        for (int i=1;i<=ct;i++){//看匹配中点数
            if(match[i]!=0){//如果这个点被匹配到了
                ans++;
            }
        }
    
        if(ans==ct){//如果匹配是一个完美匹配
            printf("YES
    ");
        }
        else printf("NO
    ");
    }
    
    int main(){
        int t,k=0;
        scanf("%d",&t);
        while(t--){
            scanf("%d%d",&n,&m);
            for (int i=1;i<=m;i++){
                scanf("%d%d",&x[i],&y[i]);
            }
            for (int i=1;i<=n;i++){
                scanf("%d",&f[i]);
            }
            printf("Case %d: ",++k);
            create();
            edmonds();
            print();
        }
        return 0;
    }
    
    你将不再是道具,而是成为人如其名的人
  • 相关阅读:
    Python之socket
    Python之创建low版的线程池
    Python之面向对象及相关
    Python之面向对象(进阶篇)
    Python之面向对象(初级篇)
    Python之线程与进程
    python中执行父类的构造方法
    python之反射
    记一次有趣的米筐经历~
    算法第四章作业
  • 原文地址:https://www.cnblogs.com/wsl-lld/p/13393578.html
Copyright © 2020-2023  润新知