单调队列优化dp
如果一个选手比你小还比你强,你就可以退役了。
有人高一了,学会了单调队列优化dp,这下是真被单调队列了😅
能用单调队列优化dp的问题一般都是滑动窗口的形式:写出朴素 dp 转移式后发现本质上是在一段长度固定且连续的单调区间中找最值(单调区间指区间移动方向是单调增减的),这个时候就能考虑单调队列优化,将复杂度降一个 。线段树能做的操作多一些,优先队列只能处理 转移过来的值与 无关的情况,但处理的区间可以不连续,线段树也是。
做题思路的话就是一定要先写出朴素dp转移式,然后观察能不能转化成类似滑动窗口的形式。dp优化题都是这样的,但是如果你发现转移式根本拆不开,根本想不到怎么优化,可以考虑重新从优化的角度设一个新的能被优化的状态。
给一个单调队列优化的模板,理解上采用了经典的选手退役模型:
h=1,t=0;
for(int i=1;i<=n;++i){
while(h<=t&&i-q[h]>k) ++h; //老年选手退役
if(h<=t) dp[i]=...; //最强选手进省队
while(h<=t&&dp[q[t]]....) --t; //被新生单调队列的学长退役
q[++t]=i; //新选手加入OI队列
}
P3572 [POI2014] PTA-Little Bird
这个题比较毒,只能单调队列优化dp不能线段树维护。朴素转移很简单:。复杂度要求 ,考虑单调队列优化。板子题不过多赘述。
P2569 [SCOI2010] 股票交易
不难的题,还是先写出朴素转移,状态是好设的:令 表示第 天有 支股票的最大收益,刷表法考虑第 天有四种转移的情况:
啥都不干,直接从前一天转移来
第一次买股票,收益为
枚举在第 天有 支股票,今天买到 支,
枚举在第 天有 支股票,今天卖到 支,
复杂度瓶颈在于枚举 的朴素转移,总复杂度 。容易发现,在固定了 之后,第三种和第四种转移就是在长度固定的连续单调区间中找最值,显然能单调队列优化,能优化到 。注意单调队列初始的指针赋为 ,这样才能让第一个元素正确。
P2034 选择数字
发现以前用优先队列过了一道单调队列优化dp的题,看来是自己做的了。
朴素转移很简单,令 表示前 个数不选 时的答案:,答案便是 。
单调队列是显然的,但我用的优先队列。考虑到转移过来的 的范围只和 有关且是单调的,于是考虑在优先队列里塞 pair(-sum[j-1]+dp[j-1],1)
,前者是只和 相关的贡献,后者是编号。当枚举到 时取堆顶元素就行了,如果无法从其转移就不断弹出。
P2254 [NOI2005] 瑰丽华尔兹
朴素dp:令 表示 时刻在 的答案,初始是 时刻,转移很容易。
这个状态是很难优化的, 和 都不好拆开维护。考虑重设状态 表示第 个时间段在 时的答案。转移是 ,这个 对不同的方向需要分讨。以当前时间段的方向向南为例,设这段时间长 ,那么具体的转移就是 ,可以考虑将定值搬到 外,写成: 。这个时候已经可以发现,转移的范围就是一段长为 的连续的区间,可以用单调队列优化掉枚举 的过程。空间的话直接开是开得下的,也可以滚动数组优化掉 这一维。注意四个方向的转移顺序和转移式都不一样。
P3800 Power收集
我傻。直接就是滑动窗口,但是 在窗口的正中间。试过不能正反两次单调队列,于是考虑在开始对每一行开始dp前就把 的元素入队,然后不断加入 的元素,在加入元素的时候就顺便把该退役的元素给弹出了。