Going Home HDU - 1533(二分图最大权匹配&&KM算法的运用)

题目链接

HDU-1533

题意

给定一个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;
}

参考博客

模板总结——二分图最大权匹配

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值