这题的状态推倒极富想象力,原来写过的代码又忘了如何去写了......
题意:其实就和以前做过的一道我要长高一模一样,给定N个数字,现在要求连续的两个数字之差的绝对值乘以C最小,每个数字可以变大,变大所带来的开销为变量的平方.
状态方程为 dp[i][j] = min(dp[i-1][k] + C*|j-k| + (j-H[i]) ^ 2)
if (j >= k) dp[i][j] = min(dp[i-1][k] - C*k) +C*j + (j-H[i])^2
if (j <= k) dp[i][j] = min(dp[i-1][k] + C*k) - C*j + (j-H[i])^2
状态的是通过 i-1 行来推导第 i 行的状态,这里的技巧在于
for (int j = H[i-1]; j <= 100; ++j) Min = min(Min, dp[i-1][j] - C*k)
这个循环来遍历 i-1 行的所有合法高度,配合下面那条语句,就能够得到满足当前的j>Min中的k
也就是Min中的保留的最小值时的k一定是小于等于当前我们枚举到的j,也就满足了第一个式子,后面就只需要判定一下当前的j是否能够有H[i]得到,也就是j >= H[i]
for (int j = 100; j >= H[i]; --j) Min = min(Min, dp[i-1][j] + C*k)
这里的边界条件不是H[i-1],因为我们要求的推导的第i行的状态数应该是从H[i] - 100因此要把边界控制成H[i]
代码如下:
#include <cstdlib> #include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define INF 0x3f3f3f3f #define MAXN 100000 using namespace std; int N, C, seq[MAXN+5]; int dp[2][105]; // dp[i][j]表示从到第i个数字高度为j时的最小值 inline int pow2(int x) { return x * x; } int DP() { int Min; memset(dp, 0x3f, sizeof (dp)); for (int j = seq[1]; j <= 100; ++j) { // 只能够增加 dp[1][j] = pow2(j-seq[1]); } for (int i = 2; i <= N; ++i) { int t = i&1; Min = INF; for (int j = 1; j <= 100; ++j) dp[t][j] = INF; // 每次都需要初始化 for (int j = seq[i-1]; j <= 100; ++j) { // 枚举上一层的状态,来推倒当前层的状态 // 从小到大枚举i-1层,则Min中保存的值对应的i-1层的高度一定小于j Min = min(Min, dp[!t][j]-C*j); if (j >= seq[i]) { dp[t][j] = min(dp[t][j], Min + C*j + pow2(j-seq[i])); } } Min = INF; for (int j = 100; j >= seq[i]; --j) { // 保证所有合法的状态都予以求值 Min = min(Min, dp[!t][j]+C*j); if (j >= seq[i]) { dp[t][j] = min(dp[t][j], Min - C*j + pow2(j-seq[i])); } } } Min = INF; for (int i = seq[N]; i <= 100; ++i) { Min = min(Min, dp[N&1][i]); } return Min; } int main() { while (scanf("%d %d", &N, &C) == 2) { for (int i = 1; i <= N; ++i) { scanf("%d", &seq[i]); } printf("%d\n", DP()); } return 0; }