天天看点

计蒜客信息学7月普及组模拟赛 - C 收获(逆向DP)

​​计蒜客信息学7月普及组模拟赛 - C 收获​​

题目

有 n 个地点,每个点有可能是果树或者亭子。初始有一个兴奋度 w,经过果树可以选择“收获” 一次,收获值为当前兴奋度

,但是兴奋度会下降

;经过亭子可以选择花费

的收获值,但是兴奋度会上升

。问从 1 走到 n,能获得最大收获值?

分析

好题

看起来跟背包问题很像,每次选择操作或者不操作,问最后最值。

首先经过一个地点选择收获或者不收获,是会改变当前兴奋值的,而兴奋值的改变还会影响后面的收获值,也就是当前操作会影响后面所有的操作,因为收获花费都跟 w 有关。也就是有后效性,正着DP肯定不行。

倒过来看,将

表示为从第

思维:将每次兴奋值的改变看作后面的所有收获的改变,也就是当前兴奋值改变后,可以看作不变,变的是后面的所有操作:

。两个等效的。

即:兴奋值一直保持在 w 的水平。

状态转移方程:

  1. 对于经过果树:

    不收获的话,就是

  2. ,收获的话,就是看作后面所有操作都降低
  3. ,而当前兴奋值还是不变,为 w ,最后加上当前收获的
  4. 经过亭子:

    道理是一样的:

前面初始化就是找到从后面数第一个果树,将他的值

作为

数组初始值。

因为亭子后面如果没有果树就没有意义,(反正后面也不会有收获)。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
#define max(a,b) a>b?a:b
typedef long long ll;

double n, k, c, w;
double a[N], id[N], dp[N];

int main()
{
    scanf("%lf%lf%lf%lf", &n, &k, &c, &w);
    for (int i = 1; i <= n; i++){
        scanf("%lf%lf", &id[i], &a[i]);
    }
    int t = n;
    while(id[t--] == 2) {}
    dp[t + 1] = a[t + 1] * w;
    for (int i = t; i >= 1; i--){
        if(id[i] == 1){                     // 果树
            dp[i] = max(dp[i + 1], dp[i + 1] * (1.0 - k * 0.01) + a[i] * w);
        }else{                              // 亭子
            dp[i] = max(dp[i + 1], dp[i + 1] * (1.0 + c * 0.01) - a[i] * w);
        }
    }
    printf("%.2lf\n", dp[1]);
    return 0;
}