二分匹配算法

本文深入探讨了二分图的概念及其最大匹配问题,介绍了匈牙利算法,并通过多个实例(如男女婚配、机器重启、DAG最小覆盖路等)展示了如何运用这些算法。此外,还讨论了最小顶点覆盖、最大独立集与最大匹配的关系,以及它们在实际问题中的转换。最后,给出了几个相关题目(如HDU2063、HDU1150等)的解题代码,帮助读者理解和掌握这些理论知识。
摘要由CSDN通过智能技术生成

学习篇

二分图以及其最大匹配

一、什么是二分图

如果一个图的顶点可以分为两个集合X、Y, 图的所有边一定是有一个顶点属于集合X,另一个顶点属于集合Y, 则称该图为“二分图”或“二部图”。

二、二分图的最大匹配

在二分图的应用中,最常见的就是求————最大匹配,很多其他问题都可以转化为匹配问题来解决。

匈牙利算法

举个例子:男女婚配(详看hdu 2063
在这里插入图片描述

遍历左边男的:
男1:遍历右边女的, 从 女1 开始,有连线并且本轮没用过,则男1 女1 匹配 为一对
男2:遍历右边女的,从 女1 开始,有连线并且本轮没用过,但女1 有对象,则看看 其对象是否有另外的选择,若有则 到目前为止一共匹配两对,若无则 只有一对

二分图最大匹配变形题

最小顶点覆盖 || 最大独立集

定义:用最少的顶点覆盖所有的边

最小顶点覆盖=匹配对数(详看hdu 1150)
最大独立集=总顶点数-匹配数

DAG图的最小路径覆盖

定义:用最少的路覆盖所有的点(详看hdu 1151

最小路覆盖=点的总数 - 最大匹配

因为:匹配一个,点就少一个
做法:X、Y集合均为相同的顶点,表示从X -> Y的顶点

水题集

hdu 2063:男女最大匹配

hdu 2063

#include <bits/stdc++.h>
using namespace std;

int K,M,N,v[510],mp[510][510],p[510];
//mp[][]记录两顶点之间的连线,p用来保存女生的对象,v标记数组

bool dfs(int i){//dfs的含义是 判断该编号i的男生能否找到对象

    for(int j=1;j<=N;j++){//遍历女生
        if(mp[i][j]&&!v[j]){//若本轮有连线且没用过的女生,则标记
            v[j]=1;
            if(!p[j] || dfs(p[j])){//若该女生没有对象 或者 本来有对象但原来的对象能找到别的女生则可以让 i编号的男生为 j编号的女生的对象
                p[j]=i;return 1;
            }
        }
    }
    return 0;//该男生没找到对象
}


int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    while(cin>>K){
        if(K==0)break;
        cin>>M>>N;
        int ans=0;
        memset(mp,0,sizeof mp);
        for(int i=1;i<=K;i++){
            int g,b;cin>>g>>b;
            mp[g][b]=1;
        }

        memset(p,0,sizeof(p));//将所有女生的对象初始化为一个值,且该值不等于任何男生的编号(-1或0)
        for(int i=1;i<=M;i++){
            memset(v,0,sizeof v);//每轮都要初始化
            if(dfs(i))ans++;//找到对象,匹配数加加
        }
        cout<<ans<<endl;;
    }
    return 0;
}

hdu 1150:机器最少重启次数

hdu 1150

题意:两个机器,初始模式均为0,变成下一个模式要重启,每个任务对于两种机器有各自的模式,求两种机器最少重启几次才能完成所有任务

本质:最少顶点覆盖,边的总数就是任务数,对应的两种机器的模式就是两个集合的点;

注意:机器本身就在0模式,所以要预处理一下:跟 0模式相连的点不需要 记录两点的连线

#include <bits/stdc++.h>
using namespace std;

int n,m,k,v[110],mp[110][110],p[110];


bool dfs(int i){
    for(int j=1;j<m;j++){
        if(mp[i][j] && !v[j]){
            v[j]=1;
            if(p[j]==-1 || dfs(p[j]) ){
                p[j]=i;return 1;
            }
        }
    }
    return 0;
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    while(cin>>n){
        if(n==0)break;
        cin>>m>>k;

        memset(mp, 0,sizeof mp);
        for(int i=0;i<k;i++){
            int a,b;cin>>i>>a>>b;
            if(a==0 ||b==0)continue;//预处理
            mp[a][b]=1;
        }
        int ans=0;
        memset(p,-1,sizeof p);//因为“对象”有编号为0的,所以初始化为-1
        for(int i=1;i<n;i++){
            memset(v,0,sizeof v);
            if(dfs(i))ans++;
        }
        cout<<ans<<endl;
    }
    return 0;
}

hdu 1151:DAG最小覆盖路

hdu 1151

题意:给路口的数量、路的数量及其连通的方式(两个路口编号)
本质:DAG图的最小覆盖路

#include <bits/stdc++.h>
using namespace std;

int n,m,p[130],mp[130][130],v[130];

bool dfs(int i){
    for(int j=1;j<=n;j++){
        if(mp[i][j]&&!v[j]){
            v[j]=1;
            if(!p[j] || dfs(p[j])){
                p[j]=i;return 1;
            }
        }
    }
    return 0;
}


int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t;cin>>t;
    while(t--){
        cin>>n>>m;

        memset(mp,0,sizeof mp);
        for(int i=1;i<=m;i++){
            int a,b;cin>>a>>b;
            mp[a][b]=1;
        }
        int ans=0;
        memset(p,0,sizeof p);
        for(int i=1;i<=n;i++){
            memset(v,0,sizeof v);
            if(dfs(i))ans++;
        }
        cout<<n-ans<<endl;
    }
    return 0;
}

hdu 1068:最大没有联系的男女个数

hdu 1068

本质:最大独立集 -> 二分图最大匹配

注意:本题并没有指明男女生的分类,那么两个集合采用的均为所有人的编号,最后结果就是:总人数-最大匹配数/2

#include <bits/stdc++.h>
using namespace std;

int n,ans,p[1000],v[1000],mp[1000][1000];

bool dfs(int i){
    for(int j=0;j<n;j++){
        if(mp[i][j]&&!v[j]){
            v[j]=1;
            if(p[j]==-1||dfs(p[j])){
                p[j]=i;return 1;
            }
        }
    }
    return 0;
}

int main(){
    while(scanf("%d",&n)==1){
        ans=0;
        memset(mp,0, sizeof mp);
        for(int i=0;i<n;i++){
            int k;scanf("%d: (%d)",&i,&k);
            while(k--){
                int a;scanf("%d",&a);
                mp[i][a]=1;
            }
        }
        memset(p,-1,sizeof p);
        for(int i=0;i<n;i++){
            memset(v,0,sizeof v);
            if(dfs(i))ans++;
        }
        printf("%d\n",n-ans/2);
    }
    return 0;
}

hdu 1281 :骑士最大放置数

hdu 1281

题意:在一个棋盘里,给出能放置棋子的格子数以及具体坐标,求骑士最多能多少个(任何棋子之间不能伤害对方);求重要点的数量,重要点:即不放该个格子,骑士最大值就变了

国际象棋中的骑士是横竖走的,两个棋子的坐标x互不相同,y坐标互不相同;即可以转化为集合X 和 集合Y 的最大匹配数

求重要点:依次将某个点删去(两点间的连线由1->0),在进行二分匹配操作,看匹配书是否改变

#include <bits/stdc++.h>
using namespace std;

int n,m,k,ans,p[110],v[110],mp[110][110];

bool dfs(int i){
    for(int j=1;j<=m;j++){
        if(mp[i][j]&&!v[j]){
            v[j]=1;
            if(!p[j] || dfs(p[j])){
                p[j]=i;return 1;
            }
        }
    }
    return 0;
}

int match(){
    int res=0;
    memset(p,0,sizeof p);
    for(int i=1;i<=n;i++){
        memset(v,0,sizeof v);
        if(dfs(i))res++;
    }
    return res;
}

int main(){
    int t=1;
    while(scanf("%d%d%d",&n,&m,&k)==3){
        memset(mp,0,sizeof mp);
        while(k--){
            int x,y;scanf("%d%d",&x,&y);
            mp[x][y]=1;
        }
        ans=match();
        int cnt=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(mp[i][j]){
                    mp[i][j]=0;
                    if(match()<ans)cnt++;
                    mp[i][j]=1;
                }
            }
        }
        printf("Board %d have %d important blanks for %d chessmen.\n",t++,cnt,ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值