题解 P1725 【琪露诺】

· · 题解

PS:本篇题解适用于别的题解看不懂的萌新们,如有错误请指出。

看完题面不少同学,一下子没了思路,这篇题解将会把我的思考过程讲述给你们听,让你们恍然大悟、豁然开朗(此处省略10086字)

不太懂如何解题的同学们试着将题面中的区间 [i+l,i+r] 更换成 [i+x] 那么这道题就只有普及-的难度了,一道比较基础的 DP 了。我们可以得到状态转移方程:

dp_i=dp_{i-x}+v_i

(其中 v_i 代表的是第 i 个数的权值)

这样我们就可以写出简化版的关键循环代码:

for(int i=x;i<=n;i++)//从x开始,前面的格子跳不到
{
    dp[i]=dp[i-x]+v[i];
    if(i+x>n) //判断下一步是否直接跳到岸上
        ans=max(dp[i],ans);//记录最优答案
}

当然这里有一个“坑”(至少我这么认为),那就是编号小于 x 的 dp 值全部为零因为是从零开始跳,零跳不到的永远不可能被跳到或从这跳出去。

我们现在再回到原问题上,原问题仅仅是将第 i 个格子下一步可以跳到第 [x+i] 个格子上改成了可以跳到区间 [i+l,i+r] 的任意格子上,为了求出最优解,转移方程也就变成了:

dp_i=max(dp_{i-r},dp_{i-r+1},dp_{i-r+2}\ldots dp_{i-l})+v_i

这里再解释一下:因为当前节点的 dp 值是由之前节点的 dp 值决定的,所以第 i 个格子可以跳到 区间 [i+l,i+r] 的任意格子上也就变成了第 i 个格子可以有 区间 [i-r,i-l] 的任意格子跳到。

现在解决方案就非常明了了,我们只需要在每次循环中求出一个定长区间最值,这里方法有很多,推荐选择的是单调队列。

我们来看样例数据:

5 2 3

0 12 3 11 7 -2

队列里一开始为空,我们模拟一下队列:

$i=3$ 时 [ $\color{red}\text{0}$ 0 ] 3 0 0 0 $i=4$ 时 0 [ 0 $\color{red}\text{3}$ ] 11 0 0,但 0 比 3 老还比 3 弱所以 0 被弹出,队列应为 0 [ $\color{yellow}\text{0}$ $\color{red}\text{3}$ ] 11 0 0 $i=5$ 时 同理为 0 0 [ $\color{yellow}\text{3}$ $\color{red}\text{11}$ ] 11 0 0 $i=6$ 时 同理为 0 0 3 [ $\color{red}\text{11}$ 11 ] 9 最后的 dp 数组为 0 0 3 11 11 9 很显然最大值是 11 。 PS:这里队列里存的是 dp 数组中对应的值,但实际为了判别是否在当前区间内,队列中存的应是编号。 关键代码: ```cpp for(int i=l;i<=n;i++)//从l开始,前面的格子跳不到 { while(h<=t&&dq[h]<i-r) h++;//删去队首不在区间内的数 while(h<=t&&dp[dq[t]]<dp[i-l]) t--;//删去队尾比新数小的数 dq[++t]=i-l;//推入编号为 i-l 的数 dp[i]=dp[dq[h]]+v[i];当前的 dp 值为区间最大值加上自己的权值 if(i+r>n) ans=max(dp[i],ans);//如果这个数下一步可以跳出的话,记录最优值 } ``` 大家明白了吗?接下来就是完整的代码:(不要 copy 哟~~~) ```cpp #include<stdio.h> #include<algorithm> using namespace std; const int N=2e5+86; int n,dp[N],l,r,ans=-0x3f3f3f3f,v[N],dq[N],h=1,t=0; //有负数所以 ans 初始化为负无穷 int main() { scanf("%d%d%d",&n,&l,&r); if(l>r) swap(l,r); //鲁棒性 for(int i=0;i<=n;i++) scanf("%d",&v[i]); for(int i=l;i<=n;i++) { while(h<=t&&dq[h]<i-r) h++; while(h<=t&&dp[dq[t]]<dp[i-l]) t--; dq[++t]=i-l; dp[i]=dp[dq[h]]+v[i]; if(i+r>n) ans=max(dp[i],ans); } printf("%d\n",ans); return 0; } ```