Slope Trick 优化
引入
对于一类二维 DP 问题,如果它的价值函数
而非函数本身,往往能够起到优化转移的效果。这种优化 DP 的思想,就称为 Slope Trick。
「斜率」
因为大多数题目中涉及的函数都只在整点处取值,所以称它为差分和斜率没有本质区别,本文按照 Slope Trick 这个名词统一称呼它为斜率。
具体题目中,斜率的维护方式可能各不相同。如果斜率的取值有限,维护斜率变化的点(即拐点)更为方便;而如果函数中
凸函数
在讨论具体的题目之前,有必要首先了解一下凸函数的基本性质,以及在对凸函数进行各种变换时,它的斜率会如何变化。
实轴上的凸函数
凸函数较为一般的定义是在

上的凸函数
如果函数
就称函数
当然,如果不等号换作
当然,函数
此时,称
简单例子
常见的凸函数的例子包括:
- 常数函数:
,其中 ; - 一次函数:
,其中 且 ; - 绝对值函数:
,其中 ; - 任何凸函数限制在某个区间上的结果,例如
。
当然,可以通过下文提到的保持凸性的变换组合出更为复杂的凸函数。
离散点集上的凸函数
算法竞赛中,很多函数仅在部分整数值处有定义。它们在一般情况下并不是(上文定义的)凸函数,因为它们的定义域不再是凸集。为了处理这种情形,需要单独定义离散点集上的函数的凸性。简单来说,需要首先对函数做线性插值,将其定义域拓展到区间,再判断它的凸性。
离散点集上的凸函数
设
-
当
时, , -
当
时,设 , ,则 -
当
时, 。
那么,如果
因为
整数集

上的凸函数的等价定义
函数
对于所有
也就是说,只要斜率(差分)单调不减,这个序列就可以看作是
凸函数的两种刻画
其实,用斜率刻画凸函数的方式也可以推广到一般情况。
凸函数的斜率刻画
设
对于任何
斜率单调不减,可以看作是凸函数的等价定义。正因为凸函数的斜率具有单调性,在维护斜率时,通常需要选择优先队列或平衡树等数据结构。
本文还会用到凸函数的另一种等价刻画。对于函数
这个区域也称为函数
凸函数的上境图刻画
函数
稍后会看到,利用上境图,可以将凸函数的卷积下确界与凸集的 Minkowski 和联系起来。
凸函数的变换
紧接着,本文介绍一些 Slope Trick 中经常遇见的保持凸性的变换。
非负线性组合
对于凸函数
因此,如果维护了凸函数
在维护斜率的问题中,往往其中一个函数的形式比较简单,此时可以通过懒标记的方式降低修改复杂度。在维护拐点的问题中,要计算
卷积下确界(Minkowski 和)
凸函数的另一种常见操作是卷积下确界。对于函数
称为
几何直观上,
在实际问题中,如果
最值操作
两个凸函数的最大值仍然是凸函数,但是,两个凸函数的最小值未必仍然是凸函数。
很多常见的最小值操作可以转化为卷积下确界:
例子
-
仍然是凸函数,因为它可以看作是卷积下确界: -
是 上的凸函数,只要 是 上的凸函数,且在有限集合 上定义的函数 也是该离散集合上的凸函数。这是因为延拓之后的函数 可以看作是卷积下确界:因此,延拓之前的函数
也是凸函数。
但并不是所有的最小值操作都保持凸性。
反例
设
在一些特殊的问题中,尽管动态规划的转移方程可以写作两个凸函数的最小值的形式,且难以转化为卷积下确界的形式,但是价值函数依然能够保持凸性。在实际处理时,通常需要结合打表和猜测找到这类问题的合理的斜率转移方式。
了解了凸函数及其常见变换后,就可以通过具体的问题理解 Slope Trick 优化 DP 的方法。本文的例题大致分为维护拐点和维护斜率两组,用于理解这两种维护方式的常见操作和实施细节。但是,正如前文所强调的那样,维护方式并不是 Slope Trick 的本质,应当根据具体的问题需要选取合适的斜率段维护方式。
维护拐点
这类问题通常出现在需要最小化若干个绝对值的和式的问题中。因为这类问题中,价值函数的斜率的绝对值并不大,因此维护斜率变化的拐点更为方便。
维护拐点是指维护分段线性函数中,斜率发生变化的点。相当于对于每个斜率为
它的最小值就是
例题:最小成本递增序列
[BalticOI 2004] Sequence 数字序列
给定长度为
解答
首先,
考虑朴素 DP 解法。设
容易得到状态转移方程为
初始状态为
- 首先,加上
,这相当于对区间 内的所有斜率段都增加 ,对区间 内的所有斜率段都增加 ; - 对得到的函数取最小值,将
变为 。根据前文分析,这相当于对 和 做卷积下确界。因为后者的斜率段只有一段,斜率为 且向右延申至无限长,将其插入 的斜率段中,相当于删除其中所有正斜率段。
明晰了这些操作后,已经可以直接用平衡树维护所有斜率段了,但代码较复杂。注意到问题中斜率每次变化至多
设
- 增加一个负斜率段的拐点
和一个正斜率段的拐点 ; - 弹出所有正斜率段的拐点
。
实际维护时,因为每次操作结束后都没有正斜率段的拐点,即斜率拐点具有形式
- 插入两次
; - 弹出堆顶。
当然,每次结束后都需要维护当前函数的最小值。因为操作结束后,没有正斜率段,函数最小值就是它在最大堆堆顶处的取值。设每次操作之前堆顶为
其中,第一项相等是因为
本题还要求输出一种最优方案。因为最后操作结束时,最优解就是堆顶,所以
时间复杂度为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
模板题:
- Codeforces 713 C. Sonya and Problem Without a Legend
- Luogu P2893 [USACO08FEB] Making the Grade G
- Luogu P4331 [BalticOI 2004] Sequence 数字序列
- Luogu P4597 序列 sequence
- AtCoder 第 2 回 ドワンゴからの挑戦状 予選 E - 花火
例题:转移带限制的情形
[NOISG 2018 Finals] Safety
给定长度为
解答
内容大致与上一个题目相仿,只是序列
由此,有状态转移方程为
起始条件为
状态转移拆解为对凸函数的操作,分两步:
- 首先对
取最值,变为 ,这相当于 与 的卷积下确界; - 再将得到的函数与
相加。
同样因为斜率每次只变化一,可以考虑维护拐点。这样,这两步操作就可以描述为:
- 将所有负斜率段向左移动
,将所有正斜率段向右移动 ; - 插入两次
。
显然,对于本题,将正负斜率段分别维护较为方便。因为操作主要集中在零斜率段附近,因此考虑使用 对顶堆,即分别用最大堆和最小堆维护负斜率段和正斜率段的拐点。拐点的整体平移操作用懒标记完成。因为第二步操作需要分别对两个堆插入一个
最后,考虑操作过程中如何更新最小值。因为第一步平移操作并不会改变最小值,所以只要考虑交换堆顶的操作即可。设
变为
过程中,函数形状不变,只是向下平移了
算法的时间复杂度仍为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
模板题:
- Luogu P4272 [CTSC2009] 序列变换
- Luogu P11598 [NOISG 2018 Finals] Safety
- AtCoder Beginner Contest 217 H - Snuketoon
- AtCoder Regular Contest 070 E - NarrowRectangles
- AtCoder Regular Contest 123 D - Inc, Dec - Decomposition
维护斜率
还有一些问题,维护斜率更为方便。这类问题通常也可以使用 反悔贪心 或模拟费用流的思想解决。费用流模型中,最小费用往往是流量的凸函数,这就为使用 Slope Trick 提供了基础。
例题:股票交易问题
Codeforces 865 D. Buy Low Sell High
给定
解答
首先考虑朴素 DP 解法。设
初始状态为
从
-
将
与函数对应的分段线性函数
(显然是凹函数)做卷积上确界; -
因为这样会导致函数在区间
内具有有限值,这与 的要求矛盾,故而需要截取函数在 内的部分。
将它们转化为斜率段的变化,就是如下两步:
- 插入长度为
、斜率为 的斜率段; - 删除斜率有限的斜率段中,斜率最大且长度为
的一段。
因为斜率段的长度总是自然数,所以不妨维护若干个长度为一的斜率段,从而只需要记录每段的斜率即可。因为只需要插入和访问最大值操作,所以只需要一个最大堆。操作分两步:
- 插入两次
; - 弹出堆顶。
还需要维护
对比该算法实现与上文 最小成本递增序列 的代码可知,该算法等价于求将股票价格变为弱递减序列的最小成本。
时间复杂度为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
模板题:
例题:搬运土石问题
[USACO16OPEN] Landscaping P
给定长度为
解答
考虑朴素 DP 解法。设
其中,函数
它显然是凸函数。该状态转移方程的含义为
- 之前
个花园净剩余泥土数量为 时,最小成本为 ; - 将净剩余(亏空)的泥土数量在
与 之间运送的成本为 ; - 通过买卖,将第
个花园的泥土数量从 调整为 ,并将净剩余泥土数量从 调整到 ,最小成本为 。
初始状态为
将函数
- 首先,加上
,得到 ; - 然后,与
做卷积下确界,得到 ; - 最后,将函数向左平移
个单位。
转化为对斜率段的操作,同样分为三步:
- 将原点左侧斜率段全体加上
,将原点右侧斜率段全体加上 ; - 将所有小于
的斜率段全部替换为 ,将所有大于 的斜率段全部替换为 ; - 将所有斜率段向左平移
个单位。
原题中
- 对左右两个栈分别打懒标记,左侧加
,右侧加 ; - 每次栈内弹出元素时,都对
取最大值,对 取最小值。如果左栈为空,则弹出 。如果右栈为空,则弹出 ; - 将左栈顶部的
个元素弹出,插入右栈;当然, 时,就反过来。
在交换栈顶时,更新答案,向左移动就减去当前斜率,向右移动就加上当前斜率。
算法复杂度为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
模板题:
- Luogu P2748 [USACO16OPEN] Landscaping P
- Kyoto University PC 2016 H - WAAAAAAAAAAAAALL
- JAG Practice Contest 2017 J - Farm Village
习题
本文的最后,提供一些各类算法竞赛中出现过的且可以使用 Slope Trick 解决的问题,以供练习。
- Luogu P3642 [APIO2016] 烟火表演
- Luogu P9962 [THUPC 2024 初赛] 一棵树
- Luogu P11317 [RMI 2021] 路径/Paths
- AtCoder Beginner Contest 383 G - Bar Cover
- Codeforces 280 D. k-Maximum Subsequence Sum
- Codeforces 280 E. Sequence Transformation
- Codeforces 802 O. April Fools' Problem (hard)
- Codeforces 1209 H. Moving Walkways
- Codeforces 1229 F. Mateusz and Escape Room
- Codeforces 1534 G. A New Beginning
- Codeforces 1787 H. Codeforces Scoreboard
- 2019 Summer Petrozavodsk Camp H. Honorable Mention
- 2018 ACM-ICPC World Finals C. Conquer The World
- 300iq Contest 3 F. Farm of Monsters
参考文献与注释
- [Tutorial] Slope Trick - zscoder
- Slope trick explained - Kuroni
- Slope Trick - USACO Guide
- [Tutorial] Intuition on Slope Trick - maomao90
本页面最近更新:2025/4/20 03:17:47,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:c-forrest
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用