题目链接: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;
}