算法学习笔记【8】| 单调队列优化DP
单调队列:就是滑动窗口,可以求出定长 RMQ,时间复杂度线性。
优化 DP
首先把dp方程写成这个样子:
<script type="math/tex;mode=inline" id="MathJax-Element-3">f[i] = max(\text{或者其他运算})\{f[j]+calc(i,j)\}</script>f[i] = max(\text{或者其他运算})\{f[j]+calc(i,j)\}
注意 calc 的计算要能只和 j 有关。
同时 j 满足一个定长的范围,也就是说转移是从一个定长区间来的。
P3572 [POI2014] PTA-Little Bird,P1725 琪露诺:完全的模版。
在此不妨以P3957 [NOIP2017 普及组] 跳房子为例。
首先意识到本题答案满足单调性,灵活性的定义也符合人类想象(越灵活,可能得到的答案就越大),于是首先二分 k 的值,然后考虑 k 确定时的做法。
根据题目灵活性的定义,实际上能跳的范围就是 <script type="math/tex;mode=inline" id="MathJax-Element-1">[\max(d-g,1),d+g]</script>[\max(d-g,1),d+g] ,满足定长区间, <script type="math/tex;mode=inline" id="MathJax-Element-2">f[i]=f[j]+s[i]</script>f[i]=f[j]+s[i] ,那么使用单调队列。
结合一下我的代码:
#include <bits/stdc++.h>
#define int long long
#define U (l+r)/2
#define F(i,a,b) for(int i=(a);i<=(b);i++)
using namespace std;
const int N=500005,M=(N<<1),inf=1e18;
int n,d,k,x[N],s[N],f[N],q[M];
int ck(int g){
int h=1,t=0,l=0;
fill(f+1,f+n+1,-inf);
F(i,1,n){
while(h<=t&&x[i]-x[q[h]]>d+g) h++;
while(l<i&&x[i]-x[l]>=d-g){
while(h<=t&&f[l]>=f[q[t]])t--;
q[++t] = l++;
}if(h<=t)f[i]=f[q[h]]+s[i];
if(f[i]>=k) return 1;
}return 0;
}
signed main(){
ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>d>>k;F(i,1,n) cin>>x[i]>>s[i];
int l=0,r=1e9,ans=-1;while(l<=r)
ck(U)?ans=U,r=U-1:l=U+1;
cout<<ans; return 0;
}
首先,对超出范围的,进行删除,然后对符合条件的,进行加入,最后直接转移答案。
CF372C Watching Fireworks is Fun:
这题有点启示作用,当 k 满足 <script type="math/tex;mode=inline" id="MathJax-Element-4">j-x
具体的,我们把状态设为 <script type="math/tex;mode=inline" id="MathJax-Element-8">f[i][j]</script>f[i][j] ,表示放第 i 个烟花的时候在 j 点能得到的最大值, <script type="math/tex;mode=inline" id="MathJax-Element-7">f[i][j]=\max\{f[i-1][k]+b[i]-\left| a[i]-j \right|\}</script>f[i][j]=\max\{f[i-1][k]+b[i]-\left| a[i]-j \right|\} ,同时 k 能走到 j 点(即定长区间的最值)。
P2569 [SCOI2010] 股票交易:
转移多一点的时候只需要去先写朴素的方程,然后优化。
注意,尽量使用数组代替deque,因为这个东西很慢(虽然有些题实测差距不大)。

浙公网安备 33010602011771号