P2466 [SDOI2008] Sue 的小球(区间dp)

P2466 [SDOI2008] Sue 的小球(区间dp)

链接:P2466 [SDOI2008] Sue 的小球
很有意思的一道题,想各种方法都无从下手,看了洛谷题解瞬间懂了。

题意

n n n 个物品,每个物品的位置在 x i x_i xi,价值为 y i y_i yi,且其价值每单位时间下降 v i v_i vi。当前你在位置 x 0 x_0 x0,每单位时间你可以移动一格距离,当你和物品重叠时可以瞬间获取该物品的价值。问获取所有物品后的最大价值 v a l val val 为多少,输出 v a l 1000 \frac {val}{1000} 1000val,保留三位小数作为答案。

思路

我们很容易想到移动的情况可能有很多种,可能一会向左一会向右,最终要拿到所有物品。我们可以想到用区间dp, f [ l ] [ r ] f[l][r] f[l][r] 为状态来进行转移,代表拿完该区间所有物品的最大价值。贪心的考虑我们拿完一个区间的物品后肯定要向外面扩展,要么停在在左端点要么停在右端点,不可能停在中间,于是我们又加一维状态 [ 0 / 1 ] [0/1] [0/1] 代表拿完当前区间物品后停在 l / r l/r l/r 端点。

重点:
此时我们会发现, f [ l ] [ r ] [ 0 / 1 ] f[l][r][0/1] f[l][r][0/1] 完全没有考虑时间对物品价值的影响,那么我们是否要再加一维代表时间呢?显然不行,这样时空复杂度都爆炸了,我们考虑如何维护时间的影响。
我们可以把每一步受到时间影响的代价算进去,因为要得到所有物品,那么当前我们没到的地方此后肯定都要经过,当前我们一步花费时间 t t t,那么没获取的物品的价值都会因此减少,并直接影响最终的答案。于是我们就有了结论:把当前每一步花费的时间对最终答案的影响直接计算进当前的答案。

具体如何维护时间的影响,可以考虑对每个物品减少的速度 v i v_i vi 维护一个前缀和,根据每一步的转移花费的时间 ∗ * 未获取的物品区间速度和, c o s t = t ∗ ∑ 1 n v i − ∑ l r v i cost = t * \sum_1^n {v_i} - \sum_l^r{v_i} cost=t1nvilrvi. 详情见代码。

代码

/*
f[i][j][2]:从起点出发已经得到 i~j段的彩蛋,且当前在i/j点的最大值
考虑到受时间影响,此状态不能表示时间,但我们可以把每一步受到时间影响的代价算进去,因为要得到所有彩蛋,那么当前我们没到的地方,每经过
时间t,彩蛋的价值都会减少,直接影响最终的答案,直接算进当前的答案中即可解决该问题
*/
#include 
using namespace std;

#define ll long long

const int N = 1010;

struct node{
    int x, y, v;
    bool operator <(const node& A)const{
        return x < A.x;
    }
}s[N];

int n, x;
ll pre[N];
ll f[N][N][2]; // 从起点出发已经得到 i~j段的彩蛋,且当前在i/j点的最大值

ll delet(int l, int r, ll t){ // 对整体的答案的影响
    return (pre[n] - pre[r] + pre[l - 1]) * t;
}

int main(){
    // ios::sync_with_stdio(false);
    // cin.tie(0); cout.tie(0);

    scanf("%d%d", &n, &x);

    for(int i = 1; i <= n; i ++) scanf("%d", &s[i].x);
    for(int i = 1; i <= n; i ++) scanf("%d", &s[i].y);
    for(int i = 1; i <= n; i ++) scanf("%d", &s[i].v);

    sort(s + 1, s + 1 + n); // 按坐标排序
    
    memset(f, -0x3f, sizeof f);

    for(int i = 1; i <= n; i ++){ // 前缀和
        pre[i] = pre[i - 1] + s[i].v;
    }

    for(int i = 1; i <= n; i ++){
        f[i][i][0] = f[i][i][1] = 1LL * s[i].y - pre[n] * (abs(s[i].x - x));
    }

    // 区间dp
    for(int i = 2; i <= n; i ++){
        for(int l = 1; l + i - 1 <= n; l ++){
            int r = l + i - 1;
            f[l][r][0] = max(f[l + 1][r][0] - delet(l + 1, r, s[l + 1].x - s[l].x), f[l + 1][r][1] - delet(l + 1, r, s[r].x - s[l].x)) + s[l].y;
            f[l][r][1] = max(f[l][r - 1][0] - delet(l, r - 1, s[r].x - s[l].x), f[l][r - 1][1] - delet(l, r - 1, s[r].x - s[r - 1].x)) + s[r].y;
            // for(int k = 0; k < 2; k ++){
            //     printf("f[%d][%d][%d] = %lld\n", l, r, k, f[l][r][k]);
            // }
        }
    }
    double ans = (double)max(f[1][n][0], f[1][n][1]) / 1000.0;
    printf("%.3lf", ans);
    return 0;
}

你可能感兴趣的:(DP,专栏,算法,c++,动态规划)