【笔记+模板】 匈牙利算法

部分内容摘自以下博客,侵删
http://blog.csdn.net/pi9nc/article/details/11848327

算法须知

匈牙利算法用于求二分图的最大匹配,也就是说,无论是有向图还是无向图,原图必须是二分图
(以下把二分图的两部分分为左部右部)
匹配:在图论中,一个「匹配」(matching)是一个边的集合,其中任意两条边都没有公共顶点。

最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。

完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。完美匹配一定是最大匹配。但并非每个图都存在完美匹配。

最大匹配数:最大匹配的匹配边的数目
最小点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择
最大独立数:选取最多的点,使任意所选两点均不相连
最小路径覆盖数:对于一个 DAG(有向无环图),选取最少条路径,使得每个顶点属于且仅属于一条路径。路径长可以为 0(即单个点)。

定理1:最大匹配数 = 最小点覆盖数(这是 Konig 定理)
定理2:最大匹配数 = 最大独立数
定理3:最小路径覆盖数 = 顶点数 - 最大匹配数

算法流程

遍历左部中的所有点,尝试在右部中找到它的匹配点
    清空右部所有点的标记
    找u点的匹配点过程:
        遍历右部中与u相连并且没有被标记过的点i
        修改i点标记
        如果i点没有和别的左部中的点匹配,或者i点的匹配点可以重新找到一个匹配点
        将i点和u点匹配
        返回true

标记的作用:
在找寻u的匹配时,
dfs过程中,标记并不清空
如果i点已被标记,说明,要么i点已有匹配且匹配不可更改,要么i点是之前的点指定的匹配

存图方式任意,以下例题由于n较小,均采取邻接矩阵存图

模板

https://www.luogu.org/problemnew/show/3386

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1000+500;
bool mp[N][N];
int match[N];
bool book[N];
int n,m,e,sum;
bool dfs(int u){
    for(int i=0;i<=m;i++){
        if(book[i]==0&&mp[u][i]){
            book[i]=1;
            if(match[i]==0||dfs(match[i])){
                match[i]=u;
                return 1;
            }
        }

    }
    return 0;
}
int main(){
    scanf("%d%d%d",&n,&m,&e);
    for(int i=1;i<=e;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        if(v>m) continue;
        mp[u][v]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++) book[j]=0;
        if(dfs(i)) sum++;
    }
    printf("%d",sum);
    return 0;
}

例题

https://www.luogu.org/problemnew/show/2055

将人和床的关系看做二分图,求最大匹配
注意手动从自己到自己的床建一条边

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=150;
bool stt[N]/*student*/,hme[N]/*home*/,mp[N][N];
int match[N];
bool book[N],flag;
int n,T,x;
bool dfs(int u){
    for(int i=1;i<=n;i++){
        if(book[i]==0&&mp[u][i]){
            book[i]=1;
            if(match[i]==0||dfs(match[i])){
                match[i]=u;
                return 1;
            }
        }
    }
    return 0;
}
void init(){
    memset(stt,0,sizeof(stt));
    memset(hme,0,sizeof(hme));
    memset(mp,0,sizeof(mp));
    memset(match,0,sizeof(match));//一开始未清空,WA
    flag=1;
}
int main(){
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            cin>>stt[i];
        }
        for(int i=1;i<=n;i++){
            int x;
            scanf("%d",&x);
            if(stt[i]) hme[i]=x;
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                scanf("%d",&x);
                if(i==j) x=1;
                if(stt[i]&&hme[i])  continue;
                if(x&&stt[j]){
                    mp[i][j]=1;
                }
            }
        }
        for(int i=1;i<=n;i++){
            if(stt[i]&&hme[i]) continue;
            memset(book,0,sizeof(book));
            if(!dfs(i)) {
                printf("T_T\n");
                flag=0;
                break;//一开始未break 输出过多
            }
        }
        if(flag) printf("^_^\n");
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值