P1220 关路灯

学一下区间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;
}
posted @ 2018-09-16 23:18  Garen-Wang  阅读(121)  评论(0编辑  收藏  举报