【POJ - 2195】Going Home(KM算法求二分图最小权匹配)

题目链接

题意:给一张地图,标记num个人和房子的位置,每个房子只能住一个人,求所有人都进入房子至少需要多少步


解析:首先计算出每一个人到每一个房子的距离,然后就是套用KM求最佳匹配的板子。不过KM算法不好理解,这里借用一位大佬的博客:博客链接 。大佬写的十分生动,方便理解。

    以下为AC代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>

#define inf 0x3f3f3f3f

using namespace std;

char mp[101][101];

struct Node
{
    int x,y;
}house[101],man[101];

int num;
int dis[101][101];
//A,B为顶标,在算法执行最后,所有满足A[i]+B[j]=dis[i][j]的边构成的子图有完全匹配(最大权匹配)
int A[101];  //初始为所有与定点i关联的边的最大权(A,B看成入住期望值)
int B[101];  //初始为0
bool vis_man[101],vis_house[101];  //记录每一轮匹配过的人和房子
int match[101];  //记录每个房子的主人
int lack[101];  //记录离房子被看中还差多少期望

bool dfs(int i)
{
    vis_man[i]=true;
    for(int j=0;j<num;j++)
    {
        if(vis_house[j])  //房子这一轮已经有人住了
            continue;
        int t=A[i]+B[j]-dis[i][j];
        if(t==0)  //如果房子被相中了
        {
            vis_house[j]=true;
            if(match[j]==-1||dfs(match[j]))  //房子每人住或者住这个房子的人可以住其他房子
            {
                match[j]=i;  //i入住j
                return true;
            }
        }
        else
            lack[j]=min(lack[j],t);
    }
    return false;
}

int KM()
{
    memset(match,-1,sizeof(match));
    memset(B,0,sizeof(B));
    for(int i=0;i<num;i++)
    {
        dis[i][0]=abs(man[i].x-house[0].x)+abs(man[i].y-house[0].y);
        dis[i][0]=-dis[i][0];
        A[i]=dis[i][0];
        for(int j=1;j<num;j++)
        {
            dis[i][j]=abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y);
            dis[i][j]=-dis[i][j];   //因为KM算法求的是最大费,所以这里全部取反
            A[i]=max(A[i],dis[i][j]);
        }
    }
    for(int i=0;i<num;i++)
    {
        memset(lack,inf,sizeof(lack));
        while(1)
        {
            memset(vis_man,false,sizeof(vis_man));
            memset(vis_house,false,sizeof(vis_house));

            if(dfs(i))  //相中房子了
                break;

            int d=inf;  //如果没有相中,人对房子期望减小
            for(int j=0;j<num;j++)
                if(!vis_house[j])
                    d=min(d,lack[j]);   //寻找可以减的最大的数

            for(int j=0;j<num;j++)  //所有没有相中房子的人的期望减小
            {
                if(vis_man[j])
                    A[j]-=d;
                if(vis_house[j])  //所有被看过的房子的期望值增加
                    B[j]+=d;
                else   //因为人们期望减小,没有访问过的房子离被人看中又进了一步
                    lack[j]-=d;
            }
        }
    }
    int sum=0;
    for(int i=0;i<num;i++)
            sum+=dis[match[i]][i];
    return sum;
}

int main()
{
    int n,m;
    while(scanf("%d %d",&n,&m)&&(n!=0&&m!=0))
    {
        for(int i=0;i<n;i++)
            scanf("%s",mp[i]);
        int n1=0,n2=0;  //记录人数和房子数
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
            {
                if(mp[i][j]=='m')
                {
                    man[n1].x=i;
                    man[n1].y=j;
                    n1++;
                }
                else if(mp[i][j]=='H')
                {
                    house[n2].x=i;
                    house[n2].y=j;
                    n2++;
                }
            }
        num=n1;

        printf("%d\n",-KM());
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值