zoukankan      html  css  js  c++  java
  • 二分图匹配的几种实现

    二分图的最大匹配

    二分图的最大匹配有两种方法,匈牙利算法(KM算法)和最大流算法。
    我们令现有二分图G(u,v)

    • 最大流

    实质上是建模,因为你要匹配数最大,且一个点匹配上后就不能再匹配,所以我们设置超级源S和超级汇T,建图的方式如下:

    Su:flow=1vT:flow=1uv:flow=1

    因为二分图每个点只能匹配或被匹配一次,所以容量为1,然后跑ST出来的最大流即为最大匹配数。

    • 匈牙利算法

    这个算法是基于一种贪心,每次找增广路,然后它的配对次数至少增加1,多次寻找,最后的复杂度为O(nm)n点数,m为边数。

    有两种实现,第一种记录每个点对应配对哪个点,代码实现如下:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    using namespace std;
    const int M=1e6+10;
    int n,m,e;
    struct ss{
        int to,last;
        ss(int a=0,int b=0)
        :to(a),last(b){}
    }g[M<<1];
    int head[M],cnt,had[M];
    void add(int a,int b){
        g[++cnt]=ss(b,head[a]);head[a]=cnt;
        g[++cnt]=ss(a,head[b]);head[b]=cnt;
    }
    bool vis[M];
    bool find(int a){
        for(int i=head[a];i;i=g[i].last){
            if(!vis[g[i].to]){
                vis[g[i].to]=1;
                if(!had[g[i].to]||find(had[g[i].to])){
                    had[g[i].to]=a;
                    return 1;
                }
            }
        }
        return 0;
    }
    void Hungary(){
        int ans=0;
        for(int i=1;i<=n;i++){
            memset(vis,0,sizeof(vis));
            ans+=find(i);
        }
        printf("%d
    ",ans);
    }
    int u,v;
    void readIn(){
        scanf("%d%d%d",&n,&m,&e);
        while(e--){
            scanf("%d%d",&u,&v);
            if(v>m||u>n) continue;
            add(u,v+n);
        }
    }
    int main(){
        readIn();
        Hungary();
        return 0;
    }

    第二个为记录点对应哪条边是匹配上的:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int M=2000010,N=2010;
    struct ss{
        int to,last;
        ss(int a=0,int b=0):to(a),last(b){}
    }g[M];
    int head[N],cnt=1;
    void add(int a,int b){
        g[++cnt]=ss(b,head[a]);head[a]=cnt;
        g[++cnt]=ss(a,head[b]);head[b]=cnt;
    }
    int sd[N],vis[N];bool match[N];
    bool dfs(int a,bool wc){
        if(vis[a]==vis[0]) return 0;
        vis[a]=vis[0];
        if(!wc){
            if(!match[a]){return match[a]=1;}
            return dfs(g[sd[a]].to,wc^1);
        }
        for(int i=head[a];i;i=g[i].last){
            if(sd[a]==i||vis[g[i].to]==vis[0]) continue;
            if(dfs(g[i].to,wc^1)){
                sd[a]=i;sd[g[i].to]=i^1;
                return 1;
            }
        }
        return 0;
    }
    int n,m,e;
    int main(){
        int a,b;
        scanf("%d%d%d",&n,&m,&e);
        for(int i=1;i<=e;++i){
            scanf("%d%d",&a,&b);
            if(a>n||b>m)continue;
            add(a,b+n);
        }
        int bg,ed;
        if(n<m)bg=1,ed=n;else bg=n+1,ed=n+m;
        int ans=0;
        for(int i=bg;i<=ed;++i)++vis[0],match[i]=dfs(i,1),ans+=match[i];
        printf("%d
    ",ans);
        return 0;
    }

    最大流的方法(用Dinic实现较快):

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<vector>
    using namespace std;
    
    const int N=3e5+10,M=1e7+10;
    const int inf=0x7fffffff;
    int n,m,x,y,z,ans,p=1,q,end,e;
    int size[N],dis[N],f[N];
    vector <int> vec[N];
    
    struct ss{
        int to,cap;
        ss(int a=0,int b=0):to(a),cap(b){}
    }g[M];
    
    void add(int a,int b,int c){
        g[++p]=ss(b,c);vec[a].push_back(p);
        g[++p]=ss(a,0);vec[b].push_back(p);
    }
    
    int bfs(){
        memset(dis,0,sizeof(dis));
        p=q=1;
        dis[0]=1;
        f[1]=0;
        for(;p<=q;p++){
            int v=f[p];
            for(int i=0;i<vec[v].size();i++){
                int t=vec[v][i];
                if(!dis[g[t].to]&&g[t].cap){
                    dis[g[t].to]=dis[v]+1;
                    f[++q]=g[t].to;
                }
            }
        }
        return dis[end];
    }
    
    int dfs(int u,int c){
        if(u==end||!c) return c;
        int tot=0;
        for(int &i=size[u];i<vec[u].size();i++){
            int t=vec[u][i];
            if(dis[u]+1==dis[g[t].to]&&g[t].cap){
                int now=dfs(g[t].to,min(c,g[t].cap));
                g[t].cap-=now;
                g[t^1].cap+=now;
                c-=now;
                tot+=now;
                if(!c) break;
            }
        }
        return tot;
    }
    
    int main(){
        #ifndef ONLINE_JUDGE
        freopen("test.in","r",stdin);
        freopen("test.out","w",stdout);
        #endif
        scanf("%d%d%d",&n,&m,&e);
        end=n+m+1;
        for(int i=1;i<=n;i++) add(0,i,1);
        for(int i=1;i<=m;i++) add(i,end,1);
        for(int i=1;i<=e;i++){
            scanf("%d%d",&x,&y);
            if(x>n||y>m) continue;
            add(x,y+n,1);
        }
        while(bfs()){
            memset(size,0,sizeof(size));
            int k;
            while(k=dfs(0,inf)) ans+=k;
        }
        printf("%d
    ",ans-1);
        return 0;
    }

    二分图的带权匹配

    模板题目【模板】二分图带权匹配

    分为两种:最佳匹配和最大权匹配

    最佳匹配是在最大匹配下(也就是点数较少的一边完全匹配)最大的匹配值,而最大权匹配仅仅是权值最大即可。

    一般KM算法求的是最佳匹配,而转换为最大权匹配只需将没有直接连边的连一条值为0的边,进行KM算法即可。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int M=401;
    ll v[M][M],inf;
    ll A[M],B[M],ned[M];
    int VA[M],VB[M];
    int mat[M],now,n,m,e;
    bool iscon[M][M];
    bool find(int a){
        VA[a]=now;
        for(int i=1;i<=m;++i){
            if(!iscon[a][i]) continue;
            if(VB[i]==now) continue;
            ll res=A[a]+B[i]-v[a][i];
            if(!res){
                VB[i]=now;
                if(!mat[i]||find(mat[i])){
                    mat[i]=a;
                    return 1;
                }
            }else{
                ned[i]=min(ned[i],res);
            }
        }
        return 0;
    }
    ll KM(){
        memset(B,0,sizeof(B));
        for(int i=1;i<=n;i++){
            A[i]=v[i][1];
            for(int j=2;j<=m;j++){
                if(v[i][j]>A[i])A[i]=v[i][j];
            }
        }
        for(int i=1;i<=n;i++){
            memset(ned,63,sizeof(ned));inf=ned[0];
            while(1){
                ++now;
                if(find(i)) break;
                ll dec=inf;
                for(int j=1;j<=m;j++)
                    if(VB[j]!=now&&ned[j]<dec)dec=ned[j];
                for(int j=1;j<=m;j++){
                    if(VA[j]==now) A[j]-=dec;
                    if(VB[j]==now) B[j]+=dec;
                    else ned[j]-=dec;
                }
            }
        }
        ll ans=0;
        for(int i=1;i<=m;i++)
            ans+=v[mat[i]][i];
        return ans;
    }
    int vis[M],tc;
    int a,b;
    ll c;
    bool flag;
    int main(){
        scanf("%d%d%d",&n,&m,&e);
        if(n>m)swap(n,m),flag=1;
        while(e--){
            scanf("%d%d%lld",&a,&b,&c);
            if(flag)swap(a,b);
            v[a][b]=max(v[a][b],c);
            iscon[a][b]=1;
        }
        ll t1=KM();
        printf("%lld
    ",t1);
        memset(iscon,1,sizeof(iscon));
        memset(mat,0,sizeof(mat));
        t1=KM();
        printf("%lld
    ",t1);
        return 0;
    }
  • 相关阅读:
    ibatisnet系列
    jQuery弹出层演示
    winform中datagridview的用法
    ASP.net:截取固定长度字符串显示在页面,多余部分显示为省略号
    hdu 4507 恨7不成妻(求l,r中与7不相关数字的平方和)
    hdu 2089 数位dp
    uestc 1307 统计数位之间相差不小于2的数的个数
    Spoj 2319 数位统计(0,1, 2^k1 这些数分成M份)
    zoj 3416 统计平衡数个数
    数位统计 sgu 390 <浅谈数位类问题>
  • 原文地址:https://www.cnblogs.com/VictoryCzt/p/10053423.html
Copyright © 2011-2022 走看看