
矩阵优化 DP#
用矩阵描述每次转移时 DP 数组的线性变换,如果每次变换转移相同,可以根据矩阵乘法的结合律先快速幂计算出总的转移矩阵。
这里矩阵乘法不只是 (+,×),实际上只要 (⊕,⊗) 满足 ⊗ 对 ⊕ 有分配律,⊗ 有结合律,⊕ 有交换律即可。
证明:
((Aw×xBx×y)Cy×z)il=y⨁k=1(x⨁j=1Aij⊗Bjk)⊗Ckl=y⨁k=1x⨁j=1Aij⊗Bjk⊗Ckl=x⨁j=1y⨁k=1Aij⊗Bjk⊗Ckl=x⨁j=1Aij⊗(y⨁k=1Bjk⊗Ckl)=(Aw×x(Bx×yCy×z))il
从上到下依次运用了左分配律、交换律、结合律以及右分配律。
比较经典的广义矩阵乘法有 (+,×),(max/min,+),(gcd,×)。
其他略去不写。
数据结构优化 DP#
DP 转移区间修改时,可以考虑放到线段树上进行,类似区间加、区间 max、区间推平等操作,可以 O(nk) 优化到 O(nk−1logn)。
其他略去不写。
倍增优化 DP#
DP 状态由走 k 步改为走 2k 步,求解时拼起来,类似 ST 表或是树上倍增的思想。
其他略去不写。
单调队列优化 DP#
对任意状态和转移复杂度的 DP 都可以优化减少枚举状态中的一维。
要求转移方程形如:
fi=maxli≤j<i{fj+Ai+Bj}
其中 A 只关于 i 而 B 只关于 j,且 li 单调不降,整理后得到:
fi=Ai+maxli≤j<i{fj+Bj}
发现决策点只与其自身有关,且符合滑动窗口的类型,因此可以用单调队列维护出当前决策区间的最优决策点,可以把 O(n) 枚举优化为 O(1) 转移。
设 fi,j 为 i 天持有 j 股的最大收益,按照题目要求容易得到:
fi,j=max1≤k<i−W,j≤l≤min(j+ASi,MaxP){fk,l−(l−j)×APi}
fi,j=max1≤k<i−W,max(j−BSi,0)≤l≤j{fk,l+(j−l)×BPi}
对 k 的枚举可以前缀 max 优化掉,剩下对 l 的枚举区间分别是 [j,min(j+ASi,MaxP)],[max(j−BSi,0),j],区间的移动具有单调性,调整枚举顺序即可。
写出转移方程:
fi=i−1minj=i−k{fj+[hi≥hj]}
看似无法把转移方程独立成只与 j 有关,但是每次最多增加 1,因此当 f 值相等时 h 值较大的更优,而其余情况 f 值更小的一定不劣,符合单调性,可以单调队列优化。
斜率优化 DP#
解决形如:
fi=i−1maxj=0{fj+Ai+Bj+Ci×Dj}
其中 A,C 只关于 i 而 B,D 只关于 j,此时由于 i,j 共同产生影响,无法独立出 j 单调队列优化。
例题:Luogu-P3195 HNOI 2008 玩具装箱
设 fi 为装前 i 个玩具的最小花费,设 si=∑ij=1(Ci+1),可以写出初步转移方程:
fi=i−1minj=0{fj+(si−sj−1−L)2}
为了方便,令 L=L+1,把平方拆开:
fi=i−1minj=0{fj+s2i+s2j+L2−2Lsi+2Lsj−2sisj}
按照上面的形式分组:
fi=i−1minj=0{fj+(s2i−2Lsi)+(s2j+2Lsj)−2sisj}
假设 0≤j1<j2<i,我们思考在怎样的条件下,从 j1 转移更优。
也就是:
fj1+(s2i−2Lsi)+(s2j1+2Lsj1)−2sisj1<fj2+(s2i−2Lsi)+(s2j2+2Lsj2)−2sisj2<
A 部分可以消去,再移项:
2si(sj2−sj1)<(fj2+s2j2+2Lsj2)−(fj1+s2j1+2Lsj1)
2si<(fj2+s2j2+2Lsj2)−(fj1+s2j1+2Lsj1)sj2−sj1
不等号右边类似于一个斜率 (Yj2−Yj1)/(Xj2−Xj1) 的形式,也就是如果拿斜率 2si 的直线判断所有的 j,那么第一个满足斜率大于 2si 的点对 (j1,j2) 中 j1 就是最优决策点。因为显然在这之前的所有点对都是右侧点优于左侧点,而在这之后的所有点对都是左侧点优于右侧点,类似于具有凸性。
现在从线性规划的角度去思考这个问题,我们把转移方程的 min 去掉,改为:
fi=fj+(s2i−2Lsi)+(s2j+2Lsj)−2sisj
这样整理一下:
(fj+s2j+2Lsj)=2si×sj+(fi−s2i+2Lsi)
这符合 y=kx+b 的直线表达式,且 x,y 对应的值 sj 与 fj+s2j+2Lsj 恰好对应上面比较 j1,j2 时的形式。
这样我们相当于是拿斜率为 2si 的直线去切决策点,截距最小的一个点即为最优决策点,此时 fi−si2+2Lsi 最小,即 fi 最小。
结合上图理解,每个点表示 [0,i−1] 即已经得出 DP 值的位置,容易发现拿直线去切所有决策点时,D 点的截距最小。
现在问题时如何维护这些决策点,在本题中 Xj=sj 单调递增,因此每次都会在右侧增加一个点,考虑点 (E,F,I) 三点的关系,一条任意斜率直线从下向上切 I 过程中一定会先切到 E 或 F,也就是说 I 一定不是决策点,可以扔掉。这就形成了一个凸包。
对于取 min 的情况维护下凸包(斜率单调递增),对于取 max 的情况就要截距最大,维护上凸包(斜率单调递减)。
后面的内容均以取 min 为例子讨论。
X 与斜率 ki 均具有单调性的情况#
即当一段 (j1,j2) 斜率小于当前的斜率后就不可能再作为转移点,即判断 slope(j1,j2)≤ki。
在右侧加入新的点 i 时不断弹出直到找到第一段 (j1,j2) 满足 slope(j1,j2)<slope(j2,i),将 i 加入。
后者的操作就是单调栈维护凸包,由于前者的操作具有单调性,使用单调队列。
每次完成前者操作后,队首即为最优决策点。
时间复杂度 O(n)。
只有 X 具有单调性的情况#
维护凸包的部分同样是单调栈操作,只不过查询不具有单调性不能扔掉当前无用决策点,但每次单调栈二分即可。
时间复杂度 O(nlogn)。
没有任何单调性#
见 李超线段树学习笔记。
一些注意点#
-
在队首弹出无用决策点或是二分决策点时,判断斜率是否取等并不重要,因为平行的直线切在任何一个点上截距都不变。
-
在单调栈保持凸包斜率单调性时,应当保证凸包斜率严格单调,即当且仅当 slope(j1,j2)<slope(j2,i) 才停止弹栈。
-
关于精度问题使用 long double
可以避免绝大多数问题,但是一些题目中会出现 X 不严格单调递增的情况,此时 Xj1=Xj2 是我们在计算斜率是不想看到的。一个简单的处理是:
Yj2−Yj1Xj2−Xj1≥Yj3−Yj2Xj3−Xj2⇔(Yj2−Yj1)(Xj3−Xj2)≥(Yj3−Yj2)(Xj2−Xj1)
维护结束时间是困难的,考虑拆开贡献,如果一批任务为 [l,r],用时为 s+∑ri=lti,那么 [l,n] 的结束时间都会增加这个值,将贡献提前计算。
设 sti=∑ij=1tj,sci=∑nj=i+1cj,得到转移方程:
fi=i−1minj=0{fj+(sti−stj+s)×scj}
拆开整理成:
fj−(stj−s)×scj=sti×(−scj)+fi
这样保证 X=−scj 是单调不降的,t 可能为负,斜率 sti 单调性不保证,因此单调栈维护凸包并二分。
关键观察是如果两条弦相交,那么割掉 u 较小(如果 u 相等则是 v 较大)的情况下一定也割掉了另一条,这样有用的弦都是 u,v 递增。
设 prei,sufi 分别是 ai 的前缀 min 和 bi 的后缀 min,转移是:
fi=i−1minj=0fj+preuj+1−1×sufvi−+1
斜率优化,可以直接使用单调队列。
参考资料#
单调队列优化 DP#
斜率优化 DP#
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效