HDOJ-1533 二分图最佳完美匹配通俗解释

7 篇文章 0 订阅

用一个通俗的故事讲一下二分图最佳完美匹配。

有N个男人和N个女人。

其中男人都特有钱,能够出得起任何个女人的嫁妆。女的开始时都是身无分文。但是太有钱了女方又担心,担心男人有钱就会变坏,所以希望男的不能够太有钱,也不能够钱太少,女方此时定了一个苟刻的条件:如果某个男的和她财产之和要等于一个常量,由于每个女人对某个男的感觉不同,因而定的常量不同。

此时我们就可假设LXi (LXi = max(W ij)为第 i 个男人的财产,LYi (LYi = 0)为第i个女人的财产,W ij 为第 i 个 男人要娶第j个女人时两者的财产和常量。

第一个男的开始找起,他会先进行一次调查,判断所有女人之中是否会有合适的。如果不适合,他就会记下对方所需要的常量和自身的财产差值(这个差值肯定比0大,因为初始时的设定),如果找到了适合的了,那么两人牵手走人。如果这个男人调查一番都没有找到适合的,那么只能扔掉自己的一部分财产了,也不能够扔太多呀,之前不是调查过了麽,扔掉最少的差值 a (LXi -=a)。那么下轮调查很明显肯定会找到一个适合的。

调查过程有可能几个男满足同一个女的条件,那么此时先到先牵手。如果某个男的调查某个女的时候,发现满足该女的条件,不过这个女已经有男人了。该男人也不愿意轻意放过这个女人,就问一下这个女人是否肯抛弃她的男人跟他走。女的还是有点喜新厌旧的,她就问好的男人,说有人喜欢上她了,你可不可以另外找一个女人。女人的男人说,那我必须找到另外一个之后才可以跟你分。于是女人的男人去找一遍,如果还真是可以另外找到一个适合的,就果断的跟女人分了,如果没有找到,就两人继续在一起,不过为了不让女人受到这个男人的下一轮调查,给了自己的一些钱给自己的女人(LXi -= a, LYi += a)(男人之间喜欢吹牛,不知觉中把自己的调查结果说出来了,女人的男人给女人的钱刚好等于该男人准备要扔掉的钱)。

最后所有人都结成了对,且所有的对的财产加起来是最多的。

为什么呢?

由于每一对初始时都是 LXi + LYi >= W ij 。调查一轮还单身的男人扔掉了自己的一部分财产a,而扔掉的这一份a刚好是最少的,也就是说这个男人下一轮找到女人和他的财产和对于他来说是最高的。因而加起来是最优的。

回归到HDOJ 的1533题,说的是有mn个小人,和hn个小屋,每个小人最后都选择个自的小屋,问当所有的小人都进到自己的小屋时所经过的路程之和最小为多小。由于KM求的是最大问题,这题问的是最小问题。可转化权值为负来求,那样就变成了求最大值问题了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 110;
const int inf = 199999999;

int lx[maxn], ly[maxn];
int link[maxn];
int visx[maxn], visy[maxn];
int w[maxn][maxn];
int hn, mn, a;
int hungry(int u)
{
	visx[u] = 1;
	for(int i = 1; i < hn; i++)
		if(!visy[i])
		{
			int t = lx[u] + ly[i] - w[u][i];
			if(t == 0)
			{
				visy[i] = 1;
				if(!link[i] || hungry(link[i]))
				{
					link[i] = u;
					return 1;
				}
			}
		else a = min(a, t);
		}
	return 0;
}
void update()
{
	int cnt = 0;
	for(int i = 1; i < hn; i++)
	{
		if(visx[i]) lx[i] -= a;
		if(visy[i]) ly[i] += a;
	}
}

int  km()
{
	int i, j;
	for(i = 1, lx[i] = -inf; i < hn; i++)
		for(j = 1; j < hn; j++) lx[i] = max(lx[i], w[i][j]);
	memset(ly, 0, sizeof(ly));
	memset(link, 0, sizeof(link));
	for(i = 1; i < hn; i++)
	{
		a = inf;
		while(1)
		{
			memset(visx, 0, sizeof(visx));
			memset(visy, 0, sizeof(visy));
			if(hungry(i))
				break;
			else update();
		}
	}
	int ans = 0;
	for(i = 1; i < hn; i++)
	{
		ans -= w[link[i]][i];
	}
	return ans;
}

int main()
{
	int m, n; 
	char ch[110];
	while(scanf("%d%d", &m, &n) && m + n)
	{
		int i, j;
		hn = mn = 1;
		for(i = 0; i < m; i++)
		{
			scanf("%s", ch);
			for(j = 0; j < n; j++)
			{
				if(ch[j] == 'H')
				{
					lx[hn] = i;
					ly[hn++] = j;
				}
				else if(ch[j] == 'm')
				{
					visx[mn] = i;
					visy[mn++] = j;
				}
			}
		}
		for(i = 1; i < hn; i++)
		{
			for(j = 1; j < mn; j++)
				w[i][j] = -abs(lx[i] - visx[j]) - abs(ly[i] - visy[j]);
		}
		printf("%d\n", km());
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值