二分图匹配的几种实现

二分图的最大匹配

二分图的最大匹配有两种方法,匈牙利算法(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;
}
原文地址:https://www.cnblogs.com/VictoryCzt/p/10053423.html