数据结构:单调队列 / dp 优化:单调队列优化 dp / 题解:luogu P1725 琪露诺

题目链接:https://www.luogu.com.cn/problem/P1725


阅读题面,容易想到 dp:定义 \(f_i\) 表示走到 \(i\) 时能获得的最大冰冻指数。初值 \(f_0=A_0\)。

转移:$$f_i=\max_{j=i-R}^{i-L}f_j+A_i$$

直接转移是 \(O(n^2)\) 的,考虑优化,发现瓶颈在于 \(\max_{j=i-R}^{i-L}f_j\)。当然可以直接线段树优化,但是难写,而且慢。

介绍一个数据结构:单调队列。求定长数组的最值,可以用单调队列解决。

当我们已经维护了 \([l,r]\) 的最大值时,如何转移到 \([l+1,r+1]\)?假设维护了一个队列,队内元素从队头至队尾单调递减,且队内元素均在区间内。那么队头就是区间的最大值。当我们新加入一个元素时,考虑哪些元素应该被剔除。假设队尾的元素比它小,那么队尾的元素还有存在的意义吗?队尾元素的下标一定比它小,而它(新加入的元素)比队尾大,那么在后面的区间队尾就不可能在成为最大值。故算法流程如下:

当新加入一个元素时:

  1. 重复删除队尾元素直到队尾元素比新元素大;
  2. 加入这个元素;
  3. 删除队头不在当前区间内的元素;
  4. 队头元素就是当前区间的最大值。
//单调队列求区间最大值
std::deque<int> q;//单调队列
std::vector<int> a;//已知数组
int k;//窗口长度

for(int i = 0; i < a.size(); i++){//依次维护区间 [i-k+1,i]
    while(!q.empty() && a[q.back()] <= a[i]) q.pop_back();
    q.push_back(i);//存储下标,方便比较
    while(!q.empty() && q.front() <= i - k) q.pop_front();
    std::cout << q.front() << std::endl;
}

求最小值也是同理,每个元素至多出队入队一次,时间复杂度 \(O(n)\)。

利用它优化 dp,就可以 \(O(1)\) 求出 \(\max_{j=i-R}^{i-L}f_j\)。代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 4e5+5;
int n, l, r, ans = INT_MIN, f[N];
deque<int> q;
signed main(){
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> l >> r >> f[0];
    for(int i = 1; i <= n + r; i++){
        int x = 0;
        if(i <= n) cin >> x;
        if(i - l >= 0){
            while(!q.empty() && f[q.back()] <= f[i - l]) q.pop_back();
            q.push_back(i - l);
            while(!q.empty() && q.front() < i - r) q.pop_front();
            f[i] = (q.empty() ? 0 : f[q.front()]) + x;
        } else f[i] = INT_MIN;
        if(i >= n) ans = max(ans, f[i]);
    }
    cout << ans << '\n';
    return 0;
}
posted @ 2026-08-22 16:47  Antony_pico  阅读(11)  评论(0)    收藏  举报