题目链接
题意
给定一个n*M的矩阵,矩阵由‘.’,’m’,’H’组成。m表示人,H表示房子,’.’表示空地。每个人回到房子的花费为人与房子的坐标差的绝对值之和。要求每个人都回到不同的房子里,计算最小的花费。
分析
以人作为左侧顶点,以房子作为右侧顶点,构建一个完全二分图。求出边的权值并将权值取反,这样就变成了最大权匹配。
代码
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
#include <cstring>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=330;
int from[maxn],w[maxn][maxn];
int lx[maxn],ly[maxn],visx[maxn],visy[maxn],slack[maxn];
int nx,ny;
///定义slack[y]=min{lx[x]+ly[y]-w[x][y]},x已访问,y未访问
bool Find(int u)
{
visx[u]=1;
for(int v=0;v<ny;v++)if(!visy[v])
{
int tmp=lx[u]+ly[v]-w[u][v];
if(tmp==0)
{
visy[v]=1;
if(from[v]==-1 || Find(from[v]))
{
from[v]=u;
return true;
}
}
else slack[v]=min(slack[v],tmp);
}
return false;
}
int KM()//求最大权匹配
{
//初始化可行顶标
for(int i=0;i<nx;i++)
{
ly[i]=0;
lx[i]=-INF;
for(int j=0;j<ny;j++)
lx[i]=max(lx[i],w[i][j]);
}
//初始化匹配边为无
memset(from,-1,sizeof(from));
for(int u=0;u<nx;u++)
{
for(int i=0;i<ny;i++) slack[i]=INF;
//找u的匹配点
while(true)
{
//增广路标记清空
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
//找到匹配点,继续下一个点的匹配
if(Find(u)) break;
//调整顶标,使匹配边还是匹配边,非匹配边的顶标和与权值差减小
int d=INF;//找最小的差
for(int i=0;i<ny;i++)if(!visy[i])
d=min(d,slack[i]);
for(int i=0;i<nx;i++)if(visx[i])
lx[i]-=d;//修改匹配边的顶标
for(int i=0;i<ny;i++)
{
if(visy[i]) ly[i]+=d;//修改匹配边的顶标
else slack[i]-=d;//维护slack
}
}
}
//根据匹配输出最大权
int ans=0;
for(int i=0;i<ny;i++)
if(from[i]!=-1) ans+=w[from[i]][i];
return ans;
}
char s[maxn][maxn];//存矩阵
typedef pair<int,int> pa;
vector<pa> H,m;//存坐标
int main()
{
int n,M;
while(~scanf("%d%d",&n,&M))
{
H.clear();//初始化
m.clear();
if(n==0 && M==0) break;
nx=ny=0;
for(int i=0; i<n; i++)
scanf("%s",s[i]);
//构建二分图
for(int i=0; i<n; i++)
for(int j=0; j<M; j++)
{
if(s[i][j]=='.')
continue;
if(s[i][j]=='m')
{
nx++;
m.push_back(make_pair(i,j));
}
else
{
ny++;
H.push_back(pa(i,j));
}
}
//权值初始化
for(int i=0;i<nx;i++)
for(int j=0;j<ny;j++)
w[i][j]=-INF;
//求出权值并取反
for(int i=0;i<m.size();i++)
for(int j=0;j<H.size();j++)
{
w[i][j]=abs(m[i].first-H[j].first)+abs(m[i].second-H[j].second);
w[i][j]=-w[i][j];
// cout<<w[i][j]<<endl;
}
//输出最大权的相反数
printf("%d\n",-KM());
}
return 0;
}