P1725 琪露诺——典型的优先队列和dp的结合,值得一看

琪露诺

题目描述

在幻想乡,琪露诺是以笨蛋闻名的冰之妖精。

某一天,琪露诺又在玩速冻青蛙,就是用冰把青蛙瞬间冻起来。但是这只青蛙比以往的要聪明许多,在琪露诺来之前就已经跑到了河的对岸。于是琪露诺决定到河岸去追青蛙。

小河可以看作一列格子依次编号为 0 0 0 N N N,琪露诺只能从编号小的格子移动到编号大的格子。而且琪露诺按照一种特殊的方式进行移动,当她在格子 i i i 时,她只移动到区间 [ i + L , i + R ] [i+L,i+R] [i+L,i+R] 中的任意一格。你问为什么她这么移动,这还不简单,因为她是笨蛋啊。

每一个格子都有一个冰冻指数 A i A_i Ai,编号为 0 0 0 的格子冰冻指数为 0 0 0。当琪露诺停留在那一格时就可以得到那一格的冰冻指数 A i A_i Ai。琪露诺希望能够在到达对岸时,获取最大的冰冻指数,这样她才能狠狠地教训那只青蛙。

但是由于她实在是太笨了,所以她决定拜托你帮它决定怎样前进。

开始时,琪露诺在编号 0 0 0 的格子上,只要她下一步的位置编号大于 N N N 就算到达对岸。

输入格式

第一行三个正整数 N , L , R N, L, R N,L,R

第二行共 N + 1 N+1 N+1 个整数,第 i i i 个数表示编号为 i − 1 i-1 i1 的格子的冰冻指数 A i − 1 A_{i-1} Ai1

输出格式

一个整数,表示最大冰冻指数。

样例 #1

样例输入 #1

5 2 3
0 12 3 11 7 -2

样例输出 #1

11

提示

对于 60 % 60\% 60% 的数据, N ≤ 1 0 4 N \le 10^4 N104

对于 100 % 100\% 100% 的数据, N ≤ 2 × 1 0 5 N \le 2\times 10^5 N2×105,$-10^3 \le A_i\le 10^3 $,$1 \le L \le R \le N $。数据保证最终答案不超过 2 31 − 1 2^{31}-1 2311

分析

这道题很容易想到是一道线性dp问题

题目要求帮助琪露诺在追逐青蛙的过程中,选择最优的移动路径,以获取最大的冰冻指数。琪露诺每次可以选择向前移动至当前位置加上 L L L R R R 的范围内的任意一个格子,并获取该格子的冰冻指数。

解题思路
动态规划: 这是一个典型的动态规划问题。我们可以用动态规划来解决琪露诺的移动路径问题。

状态定义: 定义状态 d p [ i ] dp[i] dp[i] 表示琪露诺移动至格子 i i i 时能够获取的最大冰冻指数。

状态转移方程: 对于每一个格子 i i i,可以从格子 i − L i-L iL 到格子 i − R i-R iR 的范围内选择一个格子 j j j(因为这个地方题目中有提及,每一个点可以到达, [ i + L , i + R ] [i+L,i+R] [i+L,i+R] 中的任意一个,反着推一下就出来了)使得 d p [ i ] = max ⁡ ( d p [ i ] , d p [ j ] + a [ i ] ) dp[i] = \max(dp[i], dp[j] + a[i]) dp[i]=max(dp[i],dp[j]+a[i]),其中 a [ i ] a[i] a[i] 表示格子 i i i 的冰冻指数。

初始条件: 初始时,琪露诺在编号为 0 0 0 的格子上, d p [ 0 ] = 0 dp[0] = 0 dp[0]=0

计算最终答案: 最终答案为 d p [ N ] dp[N] dp[N],表示琪露诺到达对岸时能够获取的最大冰冻指数。

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

int n, l, r, INF = -10000000;
int a[200010], dp[200100];

int main() {
    // 输入河流格子数量和琪露诺移动范围
    cin >> n >> l >> r;
    // 输入每个格子的冰冻指数
    for (int i = 0; i <= n; i++) {
        cin >> a[i];
    }
    // 初始化动态规划数组,设为一个很小的负值表示未计算过的状态
    for (int i = 1; i <= n + r; i++) {
        dp[i] = INF;
    }
    // 动态规划求解
    for (int i = l; i <= n + r; i++) { // 从第l个格子开始动态规划
        for (int j = i - r; j <= i - l; j++) { // 枚举可转移的上一个格子
            if (j < 0 || j > n) continue; // 越界情况处理
            // 更新当前格子的最大冰冻指数
            dp[i] = max(dp[i], dp[j] + a[i]);
        }
    }
    int maxn = -100000000; // 初始化最大冰冻指数
    // 计算最大冰冻指数
    for (int i = n + 1; i <= n + r; i++) { // 从对岸前r个格子开始搜索
        maxn = max(dp[i], maxn);
    }
    // 输出结果
    cout << maxn;
    return 0;
}

代码解析
头文件引入: 使用 #include<bits/stdc++.h> 引入标准库中的所有头文件,方便编程。

全局变量定义:

n, l, r: 分别表示河流格子数量、琪露诺移动的范围。
INF: 表示一个极小的负值,用于动态规划数组的初始化。
a[200010], dp[200100]: 分别表示每个格子的冰冻指数和动态规划数组。
主函数:

输入: 使用 cin 输入河流格子数量和琪露诺移动范围,并依次输入每个格子的冰冻指数。

动态规划数组初始化: 动态规划数组 dp 的初始值设为 INF,表示尚未计算过。

动态规划求解:

外层循环从第 l l l个格子开始,因为在前 l l l 个格子之前,琪露诺无法移动。
内层循环枚举可转移到当前格子 i i i 的上一个格子 j j j,注意 j j j 的范围是从 i − r i-r ir i − l i-l il,保证不越界。
更新当前格子的最大冰冻指数 d p [ i ] dp[i] dp[i],取当前的值和转移过来的值加上当前格子的冰冻指数的较大者。
计算最终答案:

从对岸前 n+1 个格子开始搜索,找到其中最大的冰冻指数,即为最终答案。
输出结果: 输出最大冰冻指数。

时间复杂度分析
动态规划求解: 总共有 O ( N × ( R − L + 1 ) ) O(N \times (R-L+1)) O(N×(RL+1)) 个状态需要计算,每个状态的计算复杂度为 O ( 1 ) O(1) O(1),因此总的时间复杂度为 O ( N × ( R − L + 1 ) ) O(N \times (R-L+1)) O(N×(RL+1))

计算最终答案: 遍历一次动态规划数组,时间复杂度为 O ( N ) O(N) O(N)

综上所述,算法的总时间复杂度为 O ( N × ( R − L + 1 ) ) O(N \times (R-L+1)) O(N×(RL+1))

但是这种算法只能过80分(这都还是你没有出错的情况下),洛谷标签中打了的,这道题还要用单调队列,也就是滑动窗口,但是——他给你打了一个 队先队列 队先队列 队先队列这样的标签,这是要干啥?

没错这道题可以用优先队列来模拟单调队列

首先维护一个大根堆,因为我们想要最大值啊!

在每次移动到格子 i 时,先将队列中所有不在范围 [ i − l , i − r ] [i-l, i-r] [il,ir] 内的元素弹出(相当于这个地方要维护一个序号变量,如果不在范围内就弹出),保持队列中只有在范围内的元素。然后将当前格子的冰冻指数与队列中的最大冰冻指数相加,更新 d p [ i ] . q dp[i].q dp[i].q的值。接着,将第 i − l + 1 i-l+1 il+1加入队列(因为下一个 i i i 要用这个值)。

这样,通过不断维护一个按照冰冻指数从大到小排列的优先队列,可以实现在每个格子处都找到范围内冰冻指数的最大值。

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

// 定义格子数量、琪露诺移动范围以及一个极小值
int n, l, r, INF = -10000000;

// 存储每个格子的冰冻指数
int a[250010];

// 定义结构体用于存储每个格子的状态
struct f {
    long long num, q;
    // 重载小于运算符,用于优先队列的比较
    friend bool operator < (f a1, f a2) {
        return a1.q < a2.q;
    }
} dp[250100];

// 优先队列,用于模拟单调队列
priority_queue<f> q;

int main() {
    // 输入格子数量、移动范围
    cin >> n >> l >> r;

    // 输入每个格子的冰冻指数
    for (int i = 0; i <= n; i++) {
        cin >> a[i];
    }

    // 初始化dp数组,设置冰冻指数为极小值
    for (int i = 1; i <= n + r; i++) {
        dp[i].q = INF;
        dp[i].num = i;
    }

    // 初始状态,琪露诺位于编号为0的格子上,冰冻指数为0
    dp[0].q = 0, dp[0].num = 0;

    // 将初始状态加入优先队列
    for (int i = 0; i < l; i++) {
        q.push(dp[i]);
    }

    // 模拟琪露诺的移动过程
    for (int i = l; i <= n + r; i++) {
        // 弹出队列中不在移动范围内的格子
        while (!q.empty() && (q.top().num > i - l || q.top().num < i - r)) q.pop();

        // 更新当前格子的冰冻指数
        dp[i].q = max(dp[i].q, q.top().q + a[i]);

        // 将当前格子加入优先队列
        q.push(dp[i - l + 1]);
    }

    // 寻找最大冰冻指数
    long long maxn = -100000000;
    for (int i = n + 1; i <= n + r; i++) {
        maxn = max(dp[i].q, maxn);
    }

    // 输出结果
    cout << maxn;

    return 0;
}

  • 17
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值