hdu 1421

题目概述

有N件物品,要搬走其中2*K件,每件物品都有重量,每次搬两个物品,所得疲劳度为两件物品重量差的平方,求最终疲劳度和的最小值

时限

1000ms/2000ms

输入

第一行两个正整数N,K,下一行N个正整数,代表每件物品的重量,输入到EOF为止

限制

2<=2*K<=N<=2000;1<=物品重量<=2^15

输出

每行一个数,疲劳度和的最小值

样例输入

2 1
1 3
4 1
1 2 3 10
4 2
1 8 9 10

样例输出

4
1
50

讨论

dp,不过有些特殊,因为一个物品只能被搬一次,因而不能简单求出差后排序相加,不过分析可得,若取一物品,要让疲劳最小,则取的另一物品必然是重量最接近的那个,换言之,每次必须取重量相邻的两件,先排序,之后构造一二维矩阵,行号代表当前考虑的物品序号,列号代表最多取这些对物品,有点像背包的二维矩阵了,按照状态转移方程dp[r][c]=min(dp[r-1][c],dp[r-2][c-1]+(nums[r]-nums[r-1])*(nums[r]-nums[r-1])),填写矩阵,不过需要注意两个地方,其一,第一列(列下标0)总是0,因为一个物品也不需要拿,其二,当列下标的二倍大于行下标时,值为INF,因为没有那么多物品可以拿,因而永远拿不完(累死了),按照这些要求,便可得出矩阵,最后取最右下角的值即是结果
等等,为什么这样是正确的?首先既然必须取相邻的两件,不妨设要取便取这一件和重量小于其的第一件,那么对于矩阵中任意位置,若拿,则要拿2件,而由于排序,这两件是相邻的,因而需要向上移动2行,同时,这也算是一对了因而需要左移1列,这就是dp[r-2][c-1],然后加上自身所得疲劳度,若不拿,则当前情况和没考虑这件物品但拿同样数量时是一样的,也就是dp[r-1][c]
另一个问题,如果不拿可以直接抄上一行的话,那一件都不拿岂不是疲劳度0?非也,对第一件物品,由于没法拿,疲劳度都是INF,对第二件,不拿就是抄下来INF,拿的话,是从0加上一个疲劳度,自然拿比较合适,对于矩阵内部的位置,拿不拿确实需要斟酌,但是同样的,由于要拿的数量超过现在考虑的物品数量时值就是INF,因而当要拿的数量刚刚等于考虑的物品数量时,实际上仍然是两个一般大小的数的和和INF比较,这也就保证了每次刚好够拿的时候一定会选择去拿,但当有盈余的时候才去考虑最优,可以通过看调试时矩阵内值的情况来确认这一点
另外,分析矩阵结构,发现每次最多用到上面两行的数据,因而矩阵只要三行足矣,至于如何做到循环利用,只要对行下标模3即可

题解状态

140MS,1736K,891 B,C++

题解代码

#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<stack>;
using namespace std;
#define INF 0x3f3f3f3f
#define maxx(a,b) ((a)>(b)?(a):(b))
#define minn(a,b) ((a)<(b)?(a):(b))
#define MAXN 1005
#define memset0(a) memset(a,0,sizeof(a))

int N, K;
int nums[MAXN * 2], dp[3][MAXN];//分别存放原始数据和dp过程处理
int fun()
{
    for (int p = 0; p < N; p++) {
        scanf("%d", &nums[p]);//input
    }
    sort(nums, nums + N);//排序以达成相邻最小
    for (int p = 0; p < 3; p++) {
        for (int i = 1; i <= K; i++)//注意下标从1开始 列0值全为0
            dp[p][i] = INF;//初始化矩阵元素为INF 这样后面比较大小才有基准
    }
    for (int p = 2; p <= N; p++)//第1件物品不用考虑 肯定拿不了 一行都是INF(除了列0)
        for (int i = 1; i <= K; i++) {//拿0对物品也不用考虑 肯定是0
            dp[p % 3][i] = minn(dp[(p - 1) % 3][i], dp[(p - 2) % 3][i - 1] + (nums[p - 1] - nums[p - 2])*(nums[p - 1] - nums[p - 2]));//这么长其实仍然是按照状态转移方程进行两数比较 不过求平方长一些 以及为节省空间而求模
        }
    return dp[N % 3][K];
}
int main(void)
{
    //freopen("vs_cin.txt", "r", stdin);
    //freopen("vs_cout.txt", "w", stdout);

    while (~scanf("%d%d", &N, &K)) {//input
        printf("%d\n", fun());//output
    }
}

EOF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值