SOJ 1013

1013. Going Home

Constraints

Time Limit: 10 secs, Memory Limit: 32 MB

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 file e.in. Each case starts with a line giving two integersN 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.mH.5 5HH..m...............mm..H7 8...H.......H.......H....mmmHmmmm...H.......H.......H....0 0

Sample Output

21028


  要求总权重最小的匹配,回溯穷举的话要遍历n!种情况,效率极低,对于这种二分图的最佳匹配问题,可以参考匈牙利算法和KM算法,套用上述算法可以写出复杂度较低的解法。关于这两种算法的具体论述可以参考以下两篇文章(较为通俗易懂)。

匈牙利算法:点击打开链接

KM算法:点击打开链接   点击打开链接

  以上算法计算的是最大权重匹配,本题要求最小权重也很类似,只要在确定man节点标号时和调整man节点和house节点的标号时取相反的操作即可。具体代码如下:

#include<iostream>
#include<cmath>
using namespace std;
#define Max_n 100//最大人或者屋子的数量 
#define inf 99999//inf为一个较大的变量,大于任意一个人和屋子之间的最大距离即可 
struct man{//man节点 
	int row;//保存人所在的行 
	int col;//保存人所在的列 
	int match;//保存这个人所匹配的屋子的下标 
	int visited;//记录这个人是否被访问过,即是否参与了某次匹配 
	int label;//记录这个人的标号 
	man(){
	}
	man(int r,int c,int m,int v,int l)
	{
		row=r;
		col=c;
		match=m;
		visited=v;
		label=l;
	}
};

struct house{//同man节点 
	int row;
	int col;
	int match;
	int visited;
	int label;
	house(){
	}
	house(int r,int c,int m,int v,int l)
	{
		row=r;
		col=c;
		match=m;
		visited=v;
		label=l;
	}
};
man M[Max_n];//保存man节点的数组 
house H[Max_n];//保存house节点的数组 
int mCount,hCount;//用来记录man和house的数量,初始均为0 
int weight[Max_n][Max_n];//权重矩阵,weight[i][j]记录下标为i的man与下标为j的house间的距离 
int ROW,COL;//保存每次输入的行数和列数 

void resetVisit()//用来在每次匹配之后,将visited重置为0,即将每个man和house重置为未参加匹配的状态 
{
	for(int i=0;i<mCount;++i)
	{
		M[i].visited=0;
		H[i].visited=0;
	}
}

void saveData()//用来读取输入的ROW*COL的阵列 
{
	mCount=hCount=0;//初始man和house的数量都为0 
	char c;
	for(int i=0;i<ROW;++i)
	{
		for(int j=0;j<COL;++j)
		{
			cin>>c;
		    if(c=='m')
		    {
			    M[mCount]=man(i,j,-1,0,inf);//将man节点存入M数组,match设为-1,,visited设为0,标号设为无穷大 
			    mCount++;
		    }
		    else if(c=='H')
		    {
		    	H[hCount]=house(i,j,-1,0,0);//标号设为0 
		    	hCount++;
			}
		}
	}
	for(int i=0;i<mCount;++i)
		{
			for(int j=0;j<mCount;++j)
			{
				weight[i][j]=abs(M[i].row-H[j].row)+abs(M[i].col-H[j].col);//计算权重矩阵 
			}
		}
}
void makeLabel()//为每个man节点和house节点设置初始标号,即label的值 
{
	for(int i=0;i<mCount;++i)
	{
	   	for(int j=0;j<mCount;++j)
		    M[i].label=min(M[i].label,weight[i][j]);//将每个man节点的标号设为它与所有house节点之间权重的最小值,house节点的标号全部设为0   
	}
}

bool DFS(int pos)//用深搜对下标为pos的man节点寻找匹配,无论是man还是house节点,在每次匹配中最多被访问一次 
{
	M[pos].visited=1;//下标为pos的man节点设为已被访问,或已参加此次匹配 
	for(int i=0;i<mCount;++i)//在H数组中从头开始寻找合适的匹配 
	{
		if(!H[i].visited)//若H[i]已被访问过则立即进入下一次循环 
		{
			if(M[pos].label+H[i].label-weight[pos][i]==0)//若man和house节点的标号相加等于它们间的权重则被视为可行的匹配 
			{
				H[i].visited=1;
				if(H[i].match==-1||DFS(H[i].match))//若H[i]尚未找到匹配或者H[i]对应的匹配能找到除了H[i]以外的匹配,则M[pos]和H[i]配对成功 
				{
					H[i].match=pos;
					M[pos].match=i;
					return true;
				}
			}
		}
	}
	return false;
}
int KM()//KM算法 
{
	saveData();
	makeLabel();
	for(int i=0;i<mCount;++i)
	{	
	    while(1)
		{
			resetVisit();//每次进行匹配前都要重置visited的值 
			if(DFS(i)) break;//找到匹配则退出循环为下一个man节点寻找匹配 
			int d=inf;//若找不到匹配则调整标号 
			for(int j=0;j<mCount;++j)//设所有参加匹配的man节点为集合S,所有未参加匹配的house节点为集合T,在S和T中各任选一个节点,计算它们间的权重减去两个节点标号之和后的最小值 
			    {
			    	if(M[j].visited)
			    	{
			    		for(int k=0;k<mCount;++k)
			    	    {
			    	    	if(!H[k].visited)
			    	    	d=min(d,abs(M[j].label+H[k].label-weight[j][k]));//d保存了那个最小值 
			    		    
					    }
					}
				}
			for(int j=0;j<mCount;++j)
			{
				if(M[j].visited) M[j].label+=d;//参与匹配的man节点标号全部加上d 
				if(H[j].visited) H[j].label-=d;//参与匹配的house节点标号全部减去d,调整后再次进行匹配,直到所有man节点都成功找到匹配后,此时的匹配就是总权重最小的匹配 
			}
			
		}
	}
	int ans=0;
	for(int i=0;i<mCount;++i)
	{
		ans+=weight[i][M[i].match];//计算总权重 
	}
	return ans;
}

int main()
{
	while(cin>>ROW>>COL&&ROW)
	{
		cout<<KM()<<endl;//输出总权重 
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值