之前我们学习过动态规划方法,但是并没有对DP进行系统细致的优化。今天来看一下DP的优化方法。
-
一、矩阵优化
线性代数教材中可能讲过,通过矩阵进行快速的重复运算(矩阵快速幂)。
(以下是矩阵快速幂的写法)
直接看题:
求斐波那契数列第n项余1e9+7后的值,其中1<=n<263
通过使用普通的动态规划(递推)显然会爆掉,矩阵加速就十分使用与这种线性的递推DP,构造矩阵再配上矩阵的快速幂,以logn的时间复杂度求出答案。
-
02 单调队列优化
多重背包的时候就可以用到,主要突出的是单调性。
讲解视频
例题:n个数的序列,求每一项前m个数的最小值,不足m项从第一项开始,1<=m<n<=1e5
可以维护一个双端队列,队列中记录序列位置,队列中的数字在序列中对应位置的值大小单调递增。每次处理完一个数将其与队列尾部比较,如果比队列尾部小或相等则弹出尾部,一直到队列为空或队列尾部比当前这个值小,将这个位置插入队列尾部。计算当前位置的答案时先将位置太小的首部弹出,然后取队列首部的位置即可。
时间复杂度O(n)。 -
03 线段树优化
具体可以见我之前写过的线段树这篇文章 -
04 斜率优化
当状态转移方程:
𝜔(i, j)包含与i有关的项和与j有关的项的乘积如下,且已知a[i]和b[i]单调递增,该如何处理?
做变换:
等式就可以看作一条直线,每个j都确定出一个点(x, y),对于当前要求解的i,其斜率已确定(且随i单增),要使dp[i]最大即要使这条直线过某一个j形成的点并使得的截距最小。
假设之前已处理完前9个点,将他们画在坐标平面上,可以发现,不论当前点a[i](斜率)是多少,能使截距最小的点一定选在标出来的四个点形成的下凸包中。且当𝑘_𝑗1𝑗2≤𝑘≤𝑘_𝑗2𝑗3时,选择j2点最优。
可以用双端队列来维护这个下凸包,当前双端队列中按顺序存放有j1~j4四个点。
判断当前计算的i的斜率是否大于𝑘_𝑗1𝑗2(队列中前两个点的斜率),若大于,则把队首弹出,因为i的斜率会越来越大,队首之后肯定也不会是最优转移点,弹出后继续判断。然后取队首作为转移点,此时直线的截距一定最小。
设刚计算完的点为j5。
判断𝑘_𝑗3𝑗4(即队列末尾两个点形成的斜率)是否大于𝑘_𝑗3𝑗5(j5与队列末尾倒数第二个点的斜率),若大于则将队尾弹出,因为加入j5后原队尾已经不在下凸包上,之后肯定不会作为最优转移点,弹出后继续判断。
将j5插入到队列的尾部。
建议使用手写的双端队列,因为会访问到首部第二个元素和尾部第二个元素,用stl里的deque会麻烦一点