学一下区间dp的套路。
这道题有一个显然的规律:关灯的时候,这些灯一定是连续的,否则答案不会更优。
这个真的很显然:既然关灯不需要时间,那就顺手关掉,留着浪费电。
所以一个重要的结论:那些关掉的灯就是一个区间!
我们可以定义一个\(dp[i][j]\)表示区间\([i, j]\)已经被关掉时已经用掉的电能。
如何更新?这里还是用填表法。
我们发现要更新的话,答案多上的一部分是从起点走到终点所需时间 乘以 那些没被关掉的灯的功率之和。
如果这样定义状态的话起点未知,不行了。
所以再加一维,\(dp[i][j][k]\)表示区间\([i, j]\)已经被关掉时,人在点\(i(k == 0)\)或在点\(j(k == 1)\)已经用掉的电能。
那么主要的问题就是如何计算那些没被关掉的灯的功率之和。
一个一个加太慢,我们使用前缀和即可。
最后的答案就是\(dp[1][n][0/1]\)。
注意一下区间dp的套路:先枚举区间长度,再枚举左端点,通过计算就能得到右端点。
如果枚举左端点再枚举右端点好像有一些奇怪的错误。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
const int maxn = 55;
int n, c;
int pos[maxn], p[maxn];
int sum[maxn];
int dp[maxn][maxn][2];
int main()
{
memset(dp, 0x3f, sizeof(dp));
scanf("%d%d", &n, &c);
for(int i = 1; i <= n; i++) scanf("%d%d", &pos[i], &p[i]);
for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + p[i];
dp[c][c][0] = dp[c][c][1] = 0;
// 区间dp模板
for(int len = 2; len <= n; len++)
{
for(int i = 1; i + len - 1 <= n; i++)
{
int j = i + len - 1;
dp[i][j][0] = std::min(dp[i + 1][j][0] + (pos[i + 1] - pos[i]) * (sum[i] + sum[n] - sum[j]), dp[i + 1][j][1] + (pos[j] - pos[i]) * (sum[i] + sum[n] - sum[j]));// [1, i] and [j + 1, n]
dp[i][j][1] = std::min(dp[i][j - 1][1] + (pos[j] - pos[j - 1]) * (sum[i - 1] + sum[n] - sum[j - 1]), dp[i][j - 1][0] + (pos[j] - pos[i]) * (sum[i - 1] + sum[n] - sum[j - 1]));// [1, i - 1] and [j, n]
}
}
printf("%d\n", std::min(dp[1][n][0], dp[1][n][1]));
return 0;
}