poj 2195 Going Home(二分图最优匹配KM算法)

Going Home
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 21805 Accepted: 11032

Description

On a grid map there are n little men and n houses. In each unit time, every little man can move one unit step, either horizontally, or vertically, to an adjacent point. For each little man, you need to pay a $1 travel fee for every step he moves, until he enters a house. The task is complicated with the restriction that each house can accommodate only one little man. 

Your task is to compute the minimum amount of money you need to pay in order to send these n little men into those n different houses. The input is a map of the scenario, a '.' means an empty space, an 'H' represents a house on that point, and am 'm' indicates there is a little man on that point. 

You can think of each point on the grid map as a quite large square, so it can hold n little men at the same time; also, it is okay if a little man steps on a grid with a house without entering that house.

Input

There are one or more test cases in the input. Each case starts with a line giving two integers N and M, where N is the number of rows of the map, and M is the number of columns. The rest of the input will be N lines describing the map. You may assume both N and M are between 2 and 100, inclusive. There will be the same number of 'H's and 'm's on the map; and there will be at most 100 houses. Input will terminate with 0 0 for N and M.

Output

For each test case, output one line with the single integer, which is the minimum amount, in dollars, you need to pay.

Sample Input

2 2
.m
H.
5 5
HH..m
.....
.....
.....
mm..H
7 8
...H....
...H....
...H....
mmmHmmmm
...H....
...H....
...H....
0 0

Sample Output

2
10
28

Source

提示

题意:

地图上有n(不超过100)个小人和房屋,他们都想到房屋里去,但是每间房屋只能容纳一个人,请求出小人走到房间的最少步程和。

思路:

无论是二分图最优匹配KM算法,还是最小费用最大流都可以。只是KM算法时图的边有些不一样。

KM算法

1.初始化可行顶标的值
2.用匈牙利算法寻找完备匹配
3.若未找到完备匹配则修改可行顶标的值
4.重复2,3直到找到相等子图的完备匹配为止

KM算法是求最大权完美匹配,如果要求最小权完美匹配怎么办?方法很简单,只需将所有的边权值取其相反数,求最大权完美匹配,匹配的值再取相反数即可。

KM算法与费用流的比较:

从理论上分析,KM算法的时间复杂度比费用流要好,但是实际上和较好的费用流算法实现比起来运行效率是差不多的,而对于十分稀疏的图,许多优秀的费用流算法效率是很高的。这并不说明KM算法不如费用流,相对而言,KM算法的编程实现也较为容易,在信息学竞赛中,编程的复杂度也是一个相当重要的需要考虑的因素。

示例程序

先是最小费用最大流:

Source Code

Problem: 2195		Code Length: 2599B
Memory: 976K		Time: 32MS
Language: GCC		Result: Accepted
#include <stdio.h>
#include <string.h>
#define MAX 1000000007
struct
{
    int x,y;
}man[100],home[100];
int numm,numh,map[202][202],cost[202][202],pre[202];
int spfa(int t)
{
    int q[80000],v[202],d[202],i,pos,f=0,top=0;
    for(i=0;t>=i;i++)
    {
        v[i]=0;
        d[i]=MAX;
    }
    d[0]=0;
    q[top]=0;
    top++;
    v[0]=1;
    while(f<top)
    {
        pos=q[f];
        v[pos]=0;
        for(i=0;t>=i;i++)
        {
            if(map[pos][i]!=0&&d[i]>d[pos]+cost[pos][i])
            {
                d[i]=d[pos]+cost[pos][i];
                pre[i]=pos;
                if(v[i]==0)
                {
                    q[top]=i;
                    top++;
                    v[i]=1;
                }
            }
        }
        f++;
    }
    if(d[t]!=MAX)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}
int maxflow(int t)
{
    int sum=0,min,i;
    min=MAX;
    while(spfa(t)==1)
    {
        for(i=t;i!=0;i=pre[i])
        {
            if(min>map[pre[i]][i])
            {
                min=map[pre[i]][i];
            }
        }
        for(i=t;i!=0;i=pre[i])
        {
            map[pre[i]][i]=map[pre[i]][i]-min;
            map[i][pre[i]]=map[i][pre[i]]+min;
            sum=sum+cost[pre[i]][i]*min;
        }
    }
    return sum;
}
int main()
{
    int n,m,i,i1,t;
    char s[101];
    scanf("%d %d",&n,&m);
    while(n!=0||m!=0)
    {
        memset(map,0,sizeof(map));
        memset(cost,0,sizeof(cost));
        numm=0;
        numh=0;
        for(i=0;n>i;i++)
        {
            scanf("%s",s);
            for(i1=0;s[i1]!='\0';i1++)
            {
                if(s[i1]=='m')
                {
                    man[numm].x=i;
                    man[numm].y=i1;
                    numm++;
                }
                else if(s[i1]=='H')
                {
                    home[numh].x=i;
                    home[numh].y=i1;
                    numh++;
                }
            }
        }
        t=numh+numm+1;				//终点
        for(i=1;numm>=i;i++)
        {
            map[0][i]=1;
            cost[0][i]=0;
        }
        for(i=1;numm>=i;i++)
        {
            for(i1=1;numh>=i1;i1++)
            {
                map[i][i1+numm]=1;
                cost[i][i1+numm]=abs(man[i-1].x-home[i1-1].x)+abs(man[i-1].y-home[i1-1].y);
                cost[i1+numm][i]=-cost[i][i1+numm];				//反向边
            }
        }
        for(i=1;numh>=i;i++)
        {
            map[i+numm][t]=1;
            cost[i+numm][t]=0;
        }
        printf("%d\n",maxflow(t));
        scanf("%d %d",&n,&m);
    }
    return 0;
}
二分图最优匹配KM算法:

Source Code

Problem: 2195		Code Length: 2738B
Memory: 420K		Time: 0MS
Language: GCC		Result: Accepted
#include <stdio.h>
#include <string.h>
#define MIN -1000000007
struct
{
    int x,y;
}man[100],home[100];
int numm,numh,map[100][100],lx[100],ly[100],sx[100],sy[100],match[100];
int path(int pos)
{
    int i;
    sx[pos]=1;
    for(i=0;numm>i;i++)
    {
        if(sy[i]==0&&lx[pos]+ly[i]==map[pos][i])
        {
            sy[i]=1;
            if(match[i]==-1||path(match[i])==1)
            {
                match[i]=pos;
                return 1;
            }
        }
    }
    return 0;
}
int km()
{
    int sum=0,i,i1,i2,max;
    for(i=0;numm>i;i++)
    {
        lx[i]=MIN;
        ly[i]=0;
        for(i1=0;numh>i1;i1++)
        {
            if(lx[i]<map[i][i1])
            {
                lx[i]=map[i][i1];
            }
        }
    }
    memset(match,-1,sizeof(match));
    for(i=0;numm>i;i++)
    {
        while(1)
        {
            memset(sx,0,sizeof(sx));
            memset(sy,0,sizeof(sy));
            if(path(i)==1)
            {
                break;
            }
            max=-MIN;
            for(i1=0;numm>i1;i1++)
            {
                if(sx[i1]==1)
                {
                    for(i2=0;numh>i2;i2++)
                    {
                        if(sy[i2]==0&&max>lx[i1]+ly[i2]-map[i1][i2])
                        {
                            max=lx[i1]+ly[i2]-map[i1][i2];
                        }
                    }
                }
            }
            for(i1=0;numm>i1;i1++)
            {
                if(sx[i1]==1)
                {
                    lx[i1]=lx[i1]-max;
                }
                if(sy[i1]==1)
                {
                    ly[i1]=ly[i1]+max;
                }
            }
        }
    }
    for(i=0;numm>i;i++)
    {
        sum=sum-map[match[i]][i];			//负边要用减号
    }
    return sum;
}
int main()
{
    int n,m,i,i1,t;
    char s[101];
    scanf("%d %d",&n,&m);
    while(n!=0||m!=0)
    {
        memset(map,0,sizeof(map));
        numm=0;
        numh=0;
        for(i=0;n>i;i++)
        {
            scanf("%s",s);
            for(i1=0;s[i1]!='\0';i1++)
            {
                if(s[i1]=='m')
                {
                    man[numm].x=i;
                    man[numm].y=i1;
                    numm++;
                }
                else if(s[i1]=='H')
                {
                    home[numh].x=i;
                    home[numh].y=i1;
                    numh++;
                }
            }
        }
        for(i=0;numm>i;i++)
        {
            for(i1=0;numh>i1;i1++)
            {
                map[i][i1]=-(abs(man[i].x-home[i1].x)+abs(man[i].y-home[i1].y));	//这里的边是负的
            }
        }
        printf("%d\n",km());
        scanf("%d %d",&n,&m);
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值