朴素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]表示连接着j的x的值
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]都初始化为0,b[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));
}
}