「区间DP」关路灯
关路灯
原题链接:关路灯
题目大意
给你一条直线,直线上有\(n\)个点,每个点每秒都有消耗能量,现在再给你个点\(m\),代表你当前的位置,现在你要去碰这些点,当你碰到这些点时,这些点就不再消耗能量,你的速度是1m/s,现在让你求碰完这些点消耗能量最少,且最少值为多少
题目题解
看了一下题,哎,这题还写了不能用贪心,那就dp咯,还发现每次只能碰一个点,那似乎可以用区间动规\(dp_{i, j}\)来表示我们目前的状态,代表已经碰了 \(i, j\) 这个区间的点时所消耗的最小能量,用小区间去合成大区间,每次又给你你得停留在哪边,两个状态肯定不够,再设一个状态 \(dp_{i, j, k}\) 表示走了 \(i, j\) 区间后在哪边。
因为有消耗,为了很快的计算这个消耗,我们可以用前缀和维护,然后套模板就好了?
推导公式如下
太长了 写了半天发现并没有空间解释 我直接写在代码里面吧
//#define fre yes
#include <cstdio>
#include <cstring>
#include <iostream>
const int N = 105;
int place[N], p[N], sum[N];
int f[N][N][2];
int main() {
static int n, m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d %d", &place[i], &p[i]);
}
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + p[i];
}
memset(f, 1, sizeof(f));
f[m][m][0] = f[m][m][1] = 0;
for (int p = 1; p < n; p++) {
for (int i = 1, j = i + p; i <= n && j <= n; i++, j++) {
f[i][j][0] = std::min(f[i + 1][j][0] + (place[i + 1] - place[i]) * (sum[n] - sum[j] + sum[i]), f[i + 1][j][1] + (place[j] - place[i]) * (sum[n] - sum[j] + sum[i]));
//用小区间更新大区间
//place[i + 1] - place[i] -> 意思是走的秒数
//sum[n] - sum[j] + sum[i] -> 计算能量消耗
f[i][j][1] = std::min(f[i][j - 1][1] + (place[j] - place[j - 1]) * (sum[n] - sum[j - 1] + sum[i - 1]), f[i][j - 1][0] + (place[j] - place[i]) * (sum[n] - sum[j - 1] + sum[i - 1]));
}
}
printf("%d\n", std::min(f[1][n][0], f[1][n][1]));
//最后的状态不知道哪边更小 比一下
return 0;
}