琪露诺
题目描述
在幻想乡,琪露诺是以笨蛋闻名的冰之妖精。
某一天,琪露诺又在玩速冻青蛙,就是用冰把青蛙瞬间冻起来。但是这只青蛙比以往的要聪明许多,在琪露诺来之前就已经跑到了河的对岸。于是琪露诺决定到河岸去追青蛙。
小河可以看作一列格子依次编号为 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 i−1 的格子的冰冻指数 A i − 1 A_{i-1} Ai−1。
输出格式
一个整数,表示最大冰冻指数。
样例 #1
样例输入 #1
5 2 3
0 12 3 11 7 -2
样例输出 #1
11
提示
对于 60 % 60\% 60% 的数据, N ≤ 1 0 4 N \le 10^4 N≤104。
对于 100 % 100\% 100% 的数据, N ≤ 2 × 1 0 5 N \le 2\times 10^5 N≤2×105,$-10^3 \le A_i\le 10^3 $,$1 \le L \le R \le N $。数据保证最终答案不超过 2 31 − 1 2^{31}-1 231−1。
分析
这道题很容易想到是一道线性dp问题
题目要求帮助琪露诺在追逐青蛙的过程中,选择最优的移动路径,以获取最大的冰冻指数。琪露诺每次可以选择向前移动至当前位置加上 L L L 到 R R R 的范围内的任意一个格子,并获取该格子的冰冻指数。
解题思路
动态规划: 这是一个典型的动态规划问题。我们可以用动态规划来解决琪露诺的移动路径问题。
状态定义: 定义状态 d p [ i ] dp[i] dp[i] 表示琪露诺移动至格子 i i i 时能够获取的最大冰冻指数。
状态转移方程: 对于每一个格子 i i i,可以从格子 i − L i-L i−L 到格子 i − R i-R i−R 的范围内选择一个格子 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
i−r 到
i
−
l
i-l
i−l,保证不越界。
更新当前格子的最大冰冻指数
d
p
[
i
]
dp[i]
dp[i],取当前的值和转移过来的值加上当前格子的冰冻指数的较大者。
计算最终答案:
从对岸前 n+1 个格子开始搜索,找到其中最大的冰冻指数,即为最终答案。
输出结果: 输出最大冰冻指数。
时间复杂度分析
动态规划求解: 总共有
O
(
N
×
(
R
−
L
+
1
)
)
O(N \times (R-L+1))
O(N×(R−L+1)) 个状态需要计算,每个状态的计算复杂度为
O
(
1
)
O(1)
O(1),因此总的时间复杂度为
O
(
N
×
(
R
−
L
+
1
)
)
O(N \times (R-L+1))
O(N×(R−L+1))。
计算最终答案: 遍历一次动态规划数组,时间复杂度为 O ( N ) O(N) O(N)。
综上所述,算法的总时间复杂度为 O ( N × ( R − L + 1 ) ) O(N \times (R-L+1)) O(N×(R−L+1))。
但是这种算法只能过80分(这都还是你没有出错的情况下),洛谷标签中打了的,这道题还要用单调队列,也就是滑动窗口,但是——他给你打了一个 队先队列 队先队列 队先队列这样的标签,这是要干啥?
没错这道题可以用优先队列来模拟单调队列
首先维护一个大根堆,因为我们想要最大值啊!
在每次移动到格子 i 时,先将队列中所有不在范围 [ i − l , i − r ] [i-l, i-r] [i−l,i−r] 内的元素弹出(相当于这个地方要维护一个序号变量,如果不在范围内就弹出),保持队列中只有在范围内的元素。然后将当前格子的冰冻指数与队列中的最大冰冻指数相加,更新 d p [ i ] . q dp[i].q dp[i].q的值。接着,将第 i − l + 1 i-l+1 i−l+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;
}