二分匹配km算法模板(带解释)

朴素km算法

今天做练习第一次接触二分匹配,看了别人的博客一点一点啃了个模板,带解释,希望让没看懂的人有点帮助,也算是要让自己记录一下,我是看这篇博客的:
[https://blog.csdn.net/qq_35442958/article/details/52215733]
然后对于代码,我想先用自己的理解介绍一下这个算法都有什么。
我做到的题也是和上面的博客一样的题,是村民买房的,从这道题入手吧;

hdu 2255 - 奔小康赚大钱

这道题大概是说,有n个村民,n栋房,他给出了一个n*n的矩阵,这个矩阵表示:第i个村民如果要买第j栋房子,那么他会出a[i][j]的价格,每个村民必须买且仅买一套房,每一套房仅能被一个村民买,问村民买房所花的钱的最大值是多少。
即使你还不清楚二分匹配是啥,如果你看懂了上面的黑体字,那么你可以将二分匹配暂时理解成上面的问题,只不过他是带权值的。

km算法

我们现在来说一下km算法是怎么解决上面的问题的。
首先km算法有一些数组要用,总共6个。分别是:
b o o l : v i s x [ i ] 表 示 x 的 访 问 标 记 , v i s y [ j ] : 表 示 y 的 访 问 标 记 bool: visx[i]表示x的访问标记,visy[j]:表示y的访问标记 bool:visx[i]x访,visy[j]:y访
i n t : l x [ i ] 表 示 x 的 标 杆 , l y [ j ] 表 示 y 的 标 杆 , b [ j ] 用 于 计 算 标 杆 应 该 怎 么 变 化 int:lx[i]表示x的标杆,ly[j]表示y的标杆,b[j]用于计算标杆应该怎么变化 int:lx[i]x,ly[j]y,b[j]
i n t : l i n k [ j ] 表 示 连 接 着 j 的 x 的 值 int: link[j]表示连接着j的x的值 int:link[j]jx

const int maxn=307;
const int inf =2e9+7;
int a[maxn][maxn];
bool visx[maxn],visy[maxn];
int lx[maxn],ly[maxn],b[maxn],link[maxn];

暂时不理解标杆是什么无所谓,它们的总和暂时的答案,最后会变成正确的答案。
这些值先要初始化,但是它们初始化的位置不同,我们先说他们的初始化是怎么样的

km算法数组的初始化

l i n k [ i ] 初 始 化 为 − 1 , 表 示 最 开 始 , 每 个 房 子 都 没 有 村 民 买 link[i]初始化为-1,表示最开始,每个房子都没有村民买 link[i]1
l y [ i ] 初 始 化 为 0 , l x [ i ] 初 始 化 为 第 i 个 村 民 对 他 要 买 的 房 子 能 出 的 最 大 价 格 ly[i]初始化为0,lx[i]初始化为第i个村民对他要买的房子能出的最大价格 ly[i]0,lx[i]i
v i s x [ i ] 和 v i s y [ i ] 都 初 始 化 为 0 , b [ i ] 初 始 化 为 i n f ( 因 为 我 们 要 取 最 小 值 ) visx[i]和visy[i]都初始化为0,b[i]初始化为inf(因为我们要取最小值) visx[i]visy[i]0b[i]inf()

for(int i=1;i<=n;i++){
        link[i]=-1;
        ly[i]=0;
        lx[i]=-inf;
        for(int j=1;j<=n;j++)
            lx[i]=max(lx[i],a[i][j]);
    }
for(int i=1;i<=n;i++){
            visx[i]=visy[i]=false;
            b[i]=inf;
        }

km算法的主要部分

它大体上分为两部分:
第一部分是主体:

int km(int n){

    for(int i=1;i<=n;i++){//初始化
        link[i]=-1;
        ly[i]=0;
        lx[i]=-inf;
        for(int j=1;j<=n;j++)
            lx[i]=max(lx[i],a[i][j]);
    }//初始化结束
    
    for(int k=1;k<=n;k++){//对于每一个村民,找到他要连接的房子
        for(int i=1;i<=n;i++){//每找一个村民都要重新初始化一遍访问值和b[i]
            visx[i]=visy[i]=false;
            b[i]=inf;
        }//初始化结束


        while(!dfs(k,n)){//*dfs是km算法的第二部分,这部分需要不断进行,直到该村民已经找到匹配的房子
        //dfs是用来判断是否需要增增广的函数,如果不用增广他会把这个村民和匹配的房子连接起来
        
            int d=inf;
            for(int i=1;i<=n;i++)
                if(!visy[i])
                    d=min(d,b[i]);
//上面这4行是用已经在dfs里计算好的b[i]数组,取其中的最小值为d,之后用来更新lx[i],ly[i]数组

            for(int i=1;i<=n;i++){
                if(visx[i]) lx[i]-=d,visx[i]=0;//
                if(visy[i]) ly[i]+=d,visy[i]=0;
            }
            //被访问过的x和y都要更新他的标杆数组,lx[i]是减去d,ly[i]是加上d
        }
    }
    
    //下面是计算结果并返回结果,计算方法就是把所有lx,ly的值相加。
    int ans=0;
    for(int i=1;i<=n;i++){
        ans+=lx[i]+ly[i];
    }
    return ans;
    
}

第二部分是深搜部分,所有的b[i]和visx[i]和visy[i]都是在这里被计算或者被标记的,link[i]也是
visy[i]实际上表示这个房子需要和某个村民匹配
dfs函数实际上是用来找村民能够匹配的房子的函数,如果能就匹配上,并return true表示不需要增广了,否则就查看

bool dfs(int x,int n){
    visx[x]=true;//立刻标记该x被访问过
    for(int i=1;i<=n;i++){//考虑该村民和每个房子的情况
    
        int j=lx[x]+ly[i]-a[x][i];//计算该村民和该房子是否匹配的关键值!!!
        //visy[i]实际上表示这个房子需要和某个村民匹配
        
        if(!visy[i]&&!j){//所以这里表示,这个房子不需要和其他村民匹配且该村民与该房子匹配
        
            visy[i]=true;//该房子需要和该村民匹配
            
            if(link[i]==-1||dfs(link[i],n)){//如果这个房子还没村民匹配
            //或者,已经和这个房子匹配的村民,找到了另外一个匹配的房子
                link[i]=x;//连接上这个村民
                return true;//连接成功!
            }
            
        }
        else b[i]=min(b[i],j);//这里是不匹配,或者该房子需要和其他村民匹配的情况
        //计算b[i],为增广做准备
    }
    return false;//没有找到匹配的房子,需要增广
}

ac代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=307;
const int inf =2e9+7;
int a[maxn][maxn];
bool visx[maxn],visy[maxn];
int lx[maxn],ly[maxn],b[maxn],link[maxn];
bool dfs(int x,int n){
    visx[x]=1;
    for(int i=1;i<=n;i++){
        int j=lx[x]+ly[i]-a[x][i];
        if(!visy[i]&&!j){
            visy[i]=true;
            if(link[i]==-1||dfs(link[i],n)){
                link[i]=x;
                return true;
            }
        }
        else b[i]=min(b[i],j);
    }
    return false;
}
int km(int n){
    for(int i=1;i<=n;i++){
        link[i]=-1;
        ly[i]=0;
        lx[i]=-inf;
        for(int j=1;j<=n;j++)
            lx[i]=max(lx[i],a[i][j]);
    }
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            visx[i]=visy[i]=false;
            b[i]=inf;
        }
        while(!dfs(k,n)){
            int d=inf;
            for(int i=1;i<=n;i++)
                if(!visy[i])
                    d=min(d,b[i]);
            for(int i=1;i<=n;i++){
                if(visx[i]) lx[i]-=d,visx[i]=0;
                if(visy[i]) ly[i]+=d,visy[i]=0;
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        ans+=lx[i]+ly[i];
    }
    return ans;
}
int main(){
    #ifdef LOCAL
    freopen("in.txt","r",stdin);
    #endif
    int n;
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                scanf("%d",&a[i][j]);
    printf("%d\n",km(n));
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值