HDU-2853 Assignment【二分图最优匹配】

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2853

题目大意:

现在有N个部队和M个任务(M>=N),每个部队完成每个任务有一点的效率,效率越高越好。但是部队已经安排了一定的计划,这时需要我们尽量用最小的变动,使得所有部队效率之和最大。求最小变动的数目和变动后和变动前效率之差。

解题思路:

这道题刚开始思路很清晰:求出变动前的效率,然后求出变动后的效率。作差就是第二个答案。用一个finish数组记录变动前每个任务匹配的部队,match记录最优情况下每个任务对应的部队。然后遍历2个数组,统计变化量即可。

感觉良好,一交,WA,是不是什么写错了?检查没错,忘初始化了?检查,还没错。就郁闷了。。

百度之,发现都是另外一种思路。无语呀。。。。。

然后仔细想之后发现这种思路是有问题的,比如现在有a边和b边,权值相同,原计划中选择的是a边,但是用km时候,可能选择的就是b边,虽然权值没变,但是变动量已经变了。这就是错误的情况。想想可以用一个数组标记已经匹配的边,然后等权值相同时先看看有没有在原匹配中出现过的权值相同的边,但是实现了一下,总是很繁琐。

So,就用网上的思路A了这道题。。鄙视

巧妙的思路:

因为我们要变动最小,所以对在原计划中的边要有一些特殊照顾,使得最优匹配时,尽量优先使用原计划的边,这样变化才能是最小的且不会影响原匹配。

根据这个思想,我们可以把每条边的权值扩大k倍,k要大于n。然后对原计划的边都+1。精华全在这里。我们来详细说明一下。

全部边都扩大了k倍,而且k比n大,这样,我们求出的最优匹配就是k倍的最大权值,只要除以k就可以得到最大权值。实现原计划的边加1,这样,在每次选择边时,这些变就 有了优势,就会优先选择这些边。假如原计划的h条边被选入了最优匹配中,这样,最优权值就是k倍的最大权值+k(原计划的每条边都+1)。但是k大于n的用意何在呢?我们发现假如原计划的边全部在匹配中,只会增加n,又n<k,所以除以k后不会影响最优匹配的最大权值之和,然后我们对k取余,就正好得到加入的原计划的边的个数。这时,我们只需要用总点数-加入的原计划的点数,就可以求得最小变动数了。

思路果然巧妙,既然自己想不出来,就见一个记住一个吧。积少成多。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;

#define N 55
#define MAX 1<<28
#define CLR(arr, what) memset(arr, what, sizeof(arr))

int map[N][N];
int lx[N], ly[N], slack[N];
bool visitx[N], visity[N];
int match[N];
int n, m;

bool Hungary(int u)
{
	int temp;
	visitx[u] = true;
	for(int i = 1; i <= m; ++i)
	{
		if(visity[i])
			continue;
		temp = lx[u] + ly[i] - map[u][i];
		if(temp == 0)
		{
			visity[i] = true;
			if(match[i] == - 1 || Hungary(match[i]))
			{
				match[i] = u;
				return true;
			}
		}
		else
			slack[i] = min(slack[i], temp);
	}
	return false;
}

bool KM_Perfect_Match()
{
	int temp;
	CLR(lx, 0);
	CLR(ly, 0);
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
			lx[i] = max(lx[i], map[i][j]);
	for(int i = 1; i <= n; ++i)
	{
		for(int j = 1; j <= m; ++j)
			slack[j] = MAX;
		while(1)
		{
			CLR(visitx, false);
			CLR(visity, false);
			if(Hungary(i))
				break;
			else
			{
				temp = MAX;
				for(int j = 1; j <= m; ++j)
					if(!visity[j])
						temp = min(temp, slack[j]);
				if(temp == MAX)
					return false;
				for(int j = 1; j <= n; ++j)
					if(visitx[j])
						lx[j] -= temp;
				for(int j = 1; j <= m; ++j)
					if(visity[j])
						ly[j] += temp;
					else
						slack[j] -= temp;
			}
		}
	}
	return true;
}

int main()
{
	int finish;
	int ans, res; //res原计划总权值,ans最优权值
	while(~scanf("%d%d", &n, &m))
	{
		res = ans = 0;
		CLR(match, -1);
		CLR(map, 0);
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= m; ++j)
			{
				scanf("%d", &map[i][j]);
				map[i][j] *= 100; //极其巧妙的建图
			}
		for(int i = 1; i <= n; ++i)
		{
			scanf("%d", &finish);
			res += map[i][finish];
			map[i][finish] += 1; //极其巧妙的建图
		}
		KM_Perfect_Match();
		for(int i = 1; i <= m; ++i)
		{
			if(match[i] != -1)
				ans += map[ match[i] ][i];
		}
		printf("%d %d\n", n - ans % 100, ans / 100 - res / 100);
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值