题解 P1725 【琪露诺】(DP + 单调队列)
1. 题目简述
琪露诺从编号为
数据范围:
2. 思路分析
朴素 DP (TLE 预警)
设
- 初始状态:
(起点不获得指数,或者根据题意 的处理,题目说 号格子指数为 ,且 号是起点),其余 值为负无穷(表示不可达)。 - 转移方程: 想要到达
,必须从某个位置 跳过来。 根据题目规则: 。 反推 的范围: 。 - 答案: 只要是从某个位置
能跳到对岸(即 ),那么 就是一个可能的答案。最终答案为 ,其中 。
复杂度分析:
外层循环枚举
单调队列优化 (Accept)
观察转移方程:
这正是滑动窗口最大值模型!我们可以用单调队列来维护区间
具体步骤:
- 维护一个单调递减的队列(存放下标),队头对应的
值最大。 - 枚举
从 到 : - 入队候选人:当前
对应的左边那个新进入窗口范围的位置是 。我们需要将 放入队列。 - 维护单调性:如果
,则弹出队尾,直到队列单调递减。 - 移除过期元素:窗口的左边界是
。如果 ,说明队头已经滑出窗口,弹出队头。 - 计算 DP:
。 - 更新答案:如果当前
满足 ,说明从 可以跳到对岸,用 更新最终答案。
- 入队候选人:当前
- 时间复杂度:
。
3. 细节处理
- 初始化:
数组要初始化为一个很小的负数(如 -1e9),因为可能是负数,且为了区分“不可达”状态。 - 数据范围:
,答案范围在 int 范围内,但为了防止加法溢出,用 int 即可(题目保证答案不超过 int 范围)。 - 答案区间:并不是
,而是所有能一步跳过 的位置的 最大值。
4. 图解推演
假设
| i | 窗口范围 |
新入队下标 |
队列状态 (存下标) | 队头 | 更新答案? | |
|---|---|---|---|---|---|---|
| 2 | 0 | {0} |
0 | 否 | ||
| 3 | 1 | {1} (其实 {0} |
0 | 是 ( |
||
| 4 | 2 | 比较 {2} |
2 | 是 ( |
||
| 5 | 3 | 比较 {3} |
3 | 是 ( |
最终比较所有满足条件的
5. AC 代码
cpp
#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
const int MAXN = 200005;
const int INF = -2e9; // 初始化负无穷
int dp[MAXN];
int a[MAXN];
int n, l, r;
int main() {
// 优化 I/O
ios::sync_with_stdio(false);
cin.tie(0);
if (!(cin >> n >> l >> r)) return 0;
// 读入 A_i,注意题目说第 i 个数是编号 i-1,
// 其实就是从 0 到 N 输入
for (int i = 0; i <= n; i++) {
cin >> a[i];
}
// 初始化 DP 数组
for (int i = 0; i <= n; i++) dp[i] = INF;
dp[0] = 0; // 起点
// 单调队列,存储下标
deque<int> q;
// 最终答案,初始化为很小的数
int ans = INF;
// DP 过程,i 从 L 开始,因为 0+L 是第一个能到达的点
for (int i = L; i <= n; i++) {
// 1. 新的候选下标进入视野: j = i - L
// 这个位置作为起跳点,可以跳到 i
int candidate = i - L;
// 只有当 candidate 是可达的时候,才有资格进入队列
// 虽然不可达的点值为 INF,比较时会被弹出去,但为了逻辑严谨
if (dp[candidate] != INF) {
// 维护单调递减队列 (如果新来的比队尾优,删队尾)
while (!q.empty() && dp[q.back()] <= dp[candidate]) {
q.pop_back();
}
q.push_back(candidate);
}
// 2. 移除过期元素
// 能跳到 i 的最远位置是 i - R
// 如果队头下标 < i - R,说明队头跳不到 i 了
if (!q.empty() && q.front() < i - r) {
q.pop_front();
}
// 3. 转移状态
// 如果队列不为空,队头就是当前窗口 [i-R, i-L] 内的最优解
if (!q.empty()) {
dp[i] = dp[q.front()] + a[i];
}
// 4. 更新最终答案
// 如果从当前位置 i 再跳一步(最小L,最大R)能超过 N,则 i 是合法的最后一步
// 只要区间 [i+L, i+R] 与 [N+1, +inf] 有交集
// 即 i + R > N
if (i + R > n) {
ans = max(ans, dp[i]);
}
}
cout << ans << endl;
return 0;
}
6. 总结
- 模型识别:区间长度固定的最值转移
单调队列。 - 坐标变换:仔细处理
的关系,确定“入队元素”是 ,“过期边界”是 。 - 边界条件:注意
初始化的负无穷要足够小,且判断答案时要看清楚题目定义的“到达对岸”。