1. 程式人生 > >2195 Going Home (構圖 最大匹配KM演算法)

2195 Going Home (構圖 最大匹配KM演算法)

題意:

‘.’ ,‘m’, ‘H’-> 通路, 人, 房子

讓所有人回到房子裡,使得總路徑最小

分析:

由題意可知,人與房的距離為權值,人和房子分別為x、y兩個集合,所以可以轉換成二部圖【權值最大】匹配問題,只要把路徑取【負值】,最後結果再取負即可

構圖之後直接使用KM演算法求解

KM演算法主要流程:

1)初始化,房子集合的頂標ly全為0,人集合的頂標lx全為最大值(人到其他房子距離),確保lx[i] + ly[j] >= Edge[i][j](第i人到第j房的距離(負值))

2)尋找完備匹配(一個集合匹配完全),使用匈牙利演算法

3)尋找失敗後,更新匹配,更新頂標

4)重複2~3,得出結果

程式碼:

#include <stdio.h>
#include <iostream>
#include <string.h>
#include <string>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <map>

using namespace std;

#define MAX 101
#define MIN -1e9
#define INF 0x7f7f7f7f

int t, n, m;

int x[MAX], y[MAX]; // 每次查詢完美匹配時候,區分使用過 -- 1, 未使用過 -- 0
int Edge[MAX][MAX]; // Edge【i】【j】 第i個人到第j個房子的距離(負值)

int lx[MAX], ly[MAX]; // 頂標

int linky[MAX]; // 匹配記錄

int path(int u) // 增廣路判斷 , 匈牙利演算法
{
	x[u] = 1;
	for(int v = 1; v<=m; v++) // 每個x遍歷所有y
	{
		if(y[v] == 1) continue;

		if(lx[u] + ly[v] == Edge[u][v]) // 可行頂標
		{
			y[v] = 1;
			if(linky[v] == -1 || path(linky[v])) // y為匹配 || 更改匹配y的x成功
			{
				linky[v] = u;
				return 1;
			}
		}
	}
	return 0;
}

void KM()
{
	memset(linky, -1, sizeof(linky)); // 初始化
	memset(ly, 0, sizeof(ly)); // y的頂標
	for(int i = 1; i<=n; i++) // x的頂標
	{
		lx[i] = -INF;
		for(int j = 1; j<=m; j++)
		{
			lx[i] = max(lx[i], Edge[i][j]);
		}
	}

	for(int i = 1; i<=n; i++) // 所有x都有增廣路 -> 完美匹配
	{
		while(1)
		{
			memset(x, 0, sizeof(x));
			memset(y, 0, sizeof(y));
			if(path(i)) break; // 增廣路成功
			int d = INF; // 失敗後,更換差值最小的邊
			for(int j = 1; j<=n; j++) // 對於已經使用的x, 在所有為使用的y中查詢新邊,且更換的差值最小
			{
				if(x[j] == 1)
				{
					for(int k = 1; k<=m; k++)
					{
						if(y[k] == 0 && d > lx[j]+ly[k]-Edge[j][k])
						{
							d = lx[j] + ly[k] - Edge[j][k];
						}
					}
				}
			}

			for(int j = 1; j<=n; j++) // x的頂標-d,確保lx[i] + ly[j] >= Edge[i][j]
			{
				if(x[j] == 1) lx[j] -= d;
			}
			for(int j = 1; j<=m; j++) <span style="font-family: Arial, Helvetica, sans-serif;"> // y的頂標+d,確保lx[i] + ly[j] >= Edge[i][j]</span>
			{
				if(y[j] == 1) ly[j] += d;
			}
		}
	}
	int ans = 0;
	for(int i = 1; i<=m; i++)
	{
		if(linky[i]!=-1) ans += Edge[linky[i]][i];
	}
	printf("%d\n", -ans);
}

struct MAN
{
	int x, y;
}man[MAX*MAX];

struct HUOSE
{
	int x, y;
}house[MAX*MAX];

int main()
{
	int i, j;
	//freopen("a.txt", "r", stdin);
	
	int nt, mt;
	while(scanf("%d%d", &nt, &mt) && nt + mt)
	{
		char mapt[MAX][MAX];
		for(i = 0; i<nt; i++)
		{
			scanf("%s", mapt[i]);
		}
		n = m = 0;
		for(i = 0; i<nt; i++)
		{
			for(j = 0; j<mt; j++)
			{
				if(mapt[i][j] == 'm')
				{
					n++;
					man[n].x = i;
					man[n].y = j;
				}
				else if(mapt[i][j] == 'H')
				{
					m++;
					house[m].x = i;
					house[m].y = j;
				}
			}
		}
		for(i = 1; i<=n; i++)
		{
			for(j = 1; j<=m; j++)
			{
				Edge[i][j] = -(abs(man[i].x - house[j].x)
					+abs(man[i].y - house[j].y));
			}
		}
		KM();
	}
	return 0;
}