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

浙公网安备 33010602011771号