数据结构:单调队列 / 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]\)?假设维护了一个队列,队内元素从队头至队尾单调递减,且队内元素均在区间内。那么队头就是区间的最大值。当我们新加入一个元素时,考虑哪些元素应该被剔除。假设队尾的元素比它小,那么队尾的元素还有存在的意义吗?队尾元素的下标一定比它小,而它(新加入的元素)比队尾大,那么在后面的区间队尾就不可能在成为最大值。故算法流程如下:
当新加入一个元素时:
- 重复删除队尾元素直到队尾元素比新元素大;
- 加入这个元素;
- 删除队头不在当前区间内的元素;
- 队头元素就是当前区间的最大值。
//单调队列求区间最大值
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;
}

浙公网安备 33010602011771号