题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1421
Problem Description
搬寝室是很累的,xhd深有体会.时间追述2006年7月9号,那天xhd迫于无奈要从27号楼搬到3号楼,因为10号要封楼了.看着寝室里的n件物品,xhd开始发呆,因为n是一个小于2000的整数,实在是太多了,于是xhd决定随便搬2*k件过去就行了.但还是会很累,因为2*k也不小是一个不大于n的整数.幸运的是xhd根据多年的搬东西的经验发现每搬一次的疲劳度是和左右手的物品的重量差的平方成正比(这里补充一句,xhd每次搬两件东西,左手一件右手一件).例如xhd左手拿重量为3的物品,右手拿重量为6的物品,则他搬完这次的疲劳度为(6-3)^2 = 9.现在可怜的xhd希望知道搬完这2*k件物品后的最佳状态是怎样的(也就是最低的疲劳度),请告诉他吧.
Input
每组输入数据有两行,第一行有两个数n,k(2<=2*k<=n<2000).第二行有n个整数分别表示n件物品的重量(重量是一个小于2^15的正整数).
Output
对应每组输入数据,输出数据只有一个表示他的最少的疲劳度,每个一行.
Sample Input
2 1 1 3
Sample Output
4
题解: 从n件物品中选k对,要求每对差平方之和最小。
先把n件物品从小到大按重量sort一遍,假设最终选择的某对物品中间空了至少1件物品没选,那把这对物品任何一件换成中间空的物品来选,这样得到的结果必定更小,所以这种情况下这对物品得相邻。
假设如果是至少2对物品相互交错的情况,
比如有4件物品重量为 1,2,3,4
最终选择2对物品分别为(1,3),(2,4)
将选择的3与2对调,即改为(1,2),(3,4),
这样结果更小,
所以用这种贪心的思想可以得出结论,最终结果每对物品肯定相邻。这样这题就好做了。
然后DP思想,dp[j]表示只有前j件物品可选择,如果第j件物品不选,那dp[j] = dp[j-1],(假设选择i对物品)
否则,那第j和j-1这对物品必选,dp[j] = dp[j-2] + (a[j]-a[j-1])^2;
以上2个值取最小就OK。
但其实第二个转移方程是错的,因为已经选了最后那对物品,dp[j-2]表示从前j-2件物品中选i对,这样就有i+1对了,当前只求选择i对的情况。
所以需要用另一个数组把上一次选择i-1对物品的结果记录下来。
另外需要注意的是,如果2*i==j,即必须选的物品数就是总物品数,那dp就直接取第二个方程即可。
#include <cstdio>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define INF 2000000000
using namespace std;
int n,k;
int dp[2005];
int a[2005];
int t[2005];// 用于保存上一次dp循环的结果
int main() {
while(scanf("%d%d",&n,&k) != EOF) {
for(int i = 1;i <= n;i++)
scanf("%d",&a[i]);
sort(a+1,a+1+n);
memset(dp,0,sizeof(dp));
// DP
for(int i = 1;i <= k;i++)
for(int j = 2 * i;j <= n;j++) {
if(j == 2*i) {
// 必须全部选完
memcpy(t,dp,sizeof(dp));
dp[j] = t[j-2] + (a[j]-a[j-1]) * (a[j]-a[j-1]);
continue;
}
dp[j] = min(dp[j-1],t[j-2] + (a[j]-a[j-1]) * (a[j]-a[j-1]));
}
printf("%d\n",dp[n]);
}
return 0;
}