zoukankan      html  css  js  c++  java
  • 图论学习:一般图最大匹配算法 带花树

    概念:带花树——用来求一般图最大匹配的算法,相比较二分图的最大匹配的匈牙利算法,带花树可以处理图中有奇环的情况,将奇环缩成一个点(算法中叫做一朵花),然后再类似于匈牙利算法通过找增广路来找这个图的最大匹配。

    具体的算法介绍可以参考这个博客,讲的很详细,我这里具体讲两个例题:

    1.1 or 2

    2.hard problem

    第二题就是第一题数据加强了,这里直接讲第二题的解法

    题意:给你一个n个点m条边的图,现在给你n个d,判断是否有一个子图使得第i个点的度数为di。

    题解:
    建图:将每个点根据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相连

    建图完成后,对这个图跑带花树找到匹配,如果这个匹配是完美匹配,则说明存在子图,输出YES。

    带花树的具体求解过程可以作为一个板子

    点击查看折叠代码块
    /*
    hdu-3551,子图点度数为任意
    
    一般图匹配_Edmond's Algorithm
    */
    #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;
    }
    

    参考链接

    你将不再是道具,而是成为人如其名的人
  • 相关阅读:
    synthetic-load-generator 一个不错的opentracing trace && metrics && logs 生成工具
    记一次php.ini配置不合理造成系统加载偏慢问题
    Data-Prepper opendistro 开源的基于es 的trace 分析工具
    使用babel-standalone 让浏览器支持es6特性
    tempo grafana 团队开源的分布式追踪框架
    grafana/agent grafana 团队开源的兼容prometheus 的agent
    k6 集成goja 的部分集成说明
    spf13/afero 通用文件系统试用
    goja 支持es6的一种方法
    salesforce 跨组织数据可见性的方案
  • 原文地址:https://www.cnblogs.com/wsl-lld/p/13393631.html
Copyright © 2011-2022 走看看