Going Home
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;
}