搬寝室 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 6019    Accepted Submission(s): 1998


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&lt;=n&lt;2000).第二行有n个整数分别表示n件物品的重量(重量是一个小于2^15的正整数).
 

Output
对应每组输入数据,输出数据只有一个表示他的最少的疲劳度,每个一行.
 

Sample Input
2 1 1 3
 

Sample Output
4
解题思路:
一开始看到这道题,虽然知道这是一道DP的题目(因为我正在做这一专题),但是怎么也找不出一个具有“最优子结构”、“重叠子问题”的问题定义。没有问题定义根本无法解题啊!由于所给出的测试实例太简单,经不起推敲,所以就自己想了一些实例。比如,
8 3
1 8 7 10 20 15 29 30
我就琢磨着,按题意是要在帮k次物品后使xhd他的疲劳度最小,而疲劳度又是取决于每次搬运时两件物品的重量差。它们的重量差越小,疲劳度越小。那现在的问题就是要找出k对物品,使这k对物品的重量差的平方和最小。既然如此,我就想着给这些物品排一下序,因为这样一来相邻两个物品的重量差就能达到最小。
1 7 8 10 15 20 29 30
那现在就是要在n个已排序物品里面,寻找k对相邻物品(物品不能有相交)使他们的差的平方和最小。这时我感觉还是不好解题,还是想不出好的问题定义!这是又想到,相邻两物品的重量差的平方是问题关键,于是我就对排序后的物品求出每对相邻物品之间的重量差的平方。于是得出了下列数据:
36 1 4 25 25 81 1
那现在问题就是在这n-1个数里面寻找k个不相邻的数使他们的和最小。啊哈!这时,我看着这问题有点眼熟,有点像是我做过的另一道题(hdu 2845:Beans),那道题里面要求最大不连续子序列和。于是觉得找到了问题定义了,就在那琢磨。细想之下,发现有了“k个数”这个限制后问题变得不一样了,这问题反而更像背包问题。(再一次)啊哈!这像极了背包问题。假设每件物品体积为1,那问题不就是“在n-1个物品里面选取若干个装进体积大小为k的背包里面,使其得到的总价值最小”嘛!但是要加个约束条件---“相邻物品不能取”。于是有下面的状态转移方程:
f[i][j] = min{f[i-1][j],f[i-2][i-1]+value[i]}
虽然对比网上其他代码比较慢,占用空间也比较大。但毕竟是我一次艰难的思考过程,记录在此,留作纪念。以后在来看看这个效率上是哪里比较差了
//网上代码453MS    8144K( http://hi.baidu.com/??arch/blog/item/ad17b0475f6918dfd1c86ad6.html
//这个代码843MS    16100KB
#include &lt;iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define M 2010
int dp[M][M];//1:M件物品;2:大小为M
int arr[M];
int val[M];
int n,k;
bool cmp(int a,int b)
{
    return a<b;
}
int getMin(int i,int t)//对第i件物品放进大小为j的背包进行决策
{
    if(i==1 && t==1)//第1件物品放进体积为1的背包中时没有约束条件
        return val[1];
    if(i&lt;=t)//因为不能取相邻物品,所以若i==t,则不可能装满背包。而很明显,i件物品也不可能装满体积为t(>i)的背包
        return -1;
    if(dp[i-2][t-1]==-1)
        return dp[i-1][t];
    if(dp[i-1][t]==-1)
        return dp[i-2][t-1]+val[i];
    return dp[i-1][t] < dp[i-2][t-1]+val[i] ? dp[i-1][t] : dp[i-2][t-1]+val[i];
}
int main()
{
//    freopen("testCase.txt","rw",stdin);
    int i,j;
    int num;
    while(cin>&gt;n&gt;&gt;k)
    {
        for(i=0;i<n;i++)
            cin>&gt;arr[i];
        sort(arr,arr+n,cmp);//从小到大排序
        for(i=0;i&lt;n-1;i++)//求相邻两数之差的平方,存到val中,共n-1个
        {
            int tmp = arr[i+1]-arr[i];
            val[i+1] = tmp*tmp;
        }
        //从n-1个数中选出k个不相邻的数,使他们的和最小
        //也即是带约束条件的01背包问题
        //有n-1物品,每件物品体积为1,现在要装进大小为k的背包中,使得到的总价值最小(必须装满)
        memset(dp,-1,sizeof(dp));
        for(i=0;i&lt;n;i++)
            dp[i][0] = 0;
        for(i=1;i&lt;n;i++)
        {
            for(j=1;j&lt;=k;j++)
            {
                dp[i][j] = getMin(i,j);
            }
        }
        cout&lt;&lt;dp[n-1][k]&lt;&lt;endl;
    }
    return 0;
}