POJ - 2195 Going Home (构图 最大匹配KM算法)

题目:http://poj.org/problem?id=2195

题意:

‘.’ ,‘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;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值