引入

对于一类二维 DP 问题,如果它的价值函数 对于每个固定的 都是 的凸函数,那么将函数 整体视为 处的状态,并维护它的差分(或斜率)

而非函数本身,往往能够起到优化转移的效果。这种优化 DP 的思想,就称为 Slope Trick。

「斜率」

因为大多数题目中涉及的函数都只在整点处取值,所以称它为差分和斜率没有本质区别,本文按照 Slope Trick 这个名词统一称呼它为斜率。

具体题目中,斜率的维护方式可能各不相同。如果斜率的取值范围较窄,维护斜率变化的点(即拐点)更为方便;而如果函数定义域较窄,维护斜率序列本身可能更为方便。更复杂的情形,可能需要同时维护每段斜率的大小和该段的长度。无论具体维护方式是什么,这类问题的本质都是利用状态转移中斜率序列变化较少这一点简化转移。因此,它们都可以称作 Slope Trick。

凸函数

在讨论具体的题目之前,有必要首先了解一下凸函数的基本性质,以及在对凸函数进行各种变换时,它的斜率会如何变化。

实轴上的凸函数

凸函数较为一般的定义是在 上给出的。

上的凸函数

如果函数 对于所有 和 都满足

就称函数 为 凸函数(convex function),其中 的运算法则规定为 乘以任何正实数或是加上任何实数都等于其自身,且对于任何实数 都有 。

当然,如果不等号换作 ,就相应地称它为凹函数 1。因为对于凹函数 ,总有 为凸函数,所以本节只考虑凸函数。

本文只考虑正常凸函数

为了避免讨论 的取值和额外的复杂分析,本文在讨论凸函数相关概念时,总是默认函数不会取到 ,且不总是 。这样的凸函数称为 正常凸函数(proper convex function)。这对于理解算法竞赛涉及的内容已经足够。

当然,函数 往往并不会对所有实数都有定义。如果函数 的定义域仅是 的子集,那么可以将它拓展为 上的函数:

此时,称 是凸函数,当且仅当相应的 满足上述凸函数的定义。因此,如果没有特别指出,本文提到的凸函数的定义域均是实数集 。显然,凸函数 只能在一个区间(即 的凸子集)上取得有限值。

简单例子

常见的凸函数的例子包括:

  1. 常数函数:,其中 ;
  2. 一次函数:,其中 且 ;
  3. 绝对值函数:,其中 ;
  4. 任何凸函数限制在某个区间上的结果,例如 (在凸分析的语境下也称作 的指示函数)。

当然,可以通过下文提到的保持凸性的变换组合出更为复杂的凸函数。

离散点集上的凸函数

算法竞赛中,很多函数仅在部分整数值处有定义。它们在一般情况下并不是(上文定义的)凸函数,因为它们的定义域不再是凸集。为了处理这种情形,需要单独定义离散点集上的函数的凸性。简单来说,需要首先对函数做线性插值,将其定义域拓展到区间,再判断它的凸性。

离散点集上的凸函数

设 为离散点集,即对任意闭区间 , 都是有限集。对于函数 ,可以定义函数 使得:

  • 当 时,,

  • 当 时,设 ,,则

  • 当 时,。

那么,如果 是 上的凸函数,就称 是 上的 凸函数。

因为 上的凸函数处理起来更为方便,所以本文在提及凸函数时,若非特别说明,指的都是 上的凸函数。如果本文中某个函数仅给出了部分整数处的取值,那么它在其他实数处的取值应由定义中的 确定,也就相当于直接讨论对应的分段线性函数 。

整数集 上的凸函数有一个更为直观的等价定义:

上的凸函数的等价定义

函数 是凸的,当且仅当

对于所有 都成立。

也就是说,只要斜率(差分)单调不减,这个序列就可以看作是 上的凸函数。

凸函数的两种刻画

其实,用斜率刻画凸函数的方式也可以推广到一般情况。

凸函数的斜率刻画

设 为 或它的离散子集,则函数 为凸函数,当且仅当斜率

对于任何 且 都是 和 的弱增函数。

斜率单调不减,可以看作是凸函数的等价定义。正因为凸函数的斜率具有单调性,在维护斜率时,通常需要选择 堆(优先队列) 或 平衡树 等数据结构。

本文还会用到凸函数的另一种等价刻画。对于函数 ,可以考察平面内函数图像上方的区域,即

这个区域也称为函数 的 上境图(epigraph)。函数的凸性,等价于它的上境图的凸性:

凸函数的上境图刻画

函数 是凸函数,当且仅当 是 内的凸集。

稍后会看到,利用上境图,可以将凸函数的卷积下确界与凸集的 Minkowski 和联系起来。

凸函数的变换

紧接着,本文介绍一些 Slope Trick 中经常遇见的保持凸性的变换。

非负线性组合

对于凸函数 和 以及非负实数 ,函数 也是凸函数。而且,

因此,如果维护了凸函数 和 的斜率,要得到它们的非负线性组合 的斜率,只需要逐段计算即可。

在维护斜率的问题中,往往其中一个函数的形式比较简单,此时可以通过懒标记的方式降低修改复杂度。在维护拐点的问题中,要计算 的斜率拐点,只需要将 和 的斜率拐点合并即可。

卷积下确界(Minkowski 和)

凸函数的另一种常见操作是卷积下确界。对于函数 和 ,函数

称为 和 的 卷积下确界2(infimal convolution)。如果 和 都是凸函数,它们的卷积下确界也是凸函数。

几何直观上, 就是 和 的 Minkowski 和。如果 和 都是分段线性函数,那么 同样是分段线性函数,且它的斜率段可以看作是 和 的斜率段合并(再排序)的结果。

在实际问题中,如果 和 其中一个的斜率段数较少,可以直接将较少的斜率段插入到较多的斜率段中;否则,可能需要利用 启发式合并 或 可并堆 等方法,降低合并的整体复杂度,或者根据具体问题寻找相应的处理方式。

最值操作

两个凸函数的最大值仍然是凸函数,但是,两个凸函数的最小值未必仍然是凸函数。

很多常见的最小值操作可以转化为卷积下确界:

例子

  • 仍然是凸函数,因为它可以看作是卷积下确界:

  • 是 上的凸函数,只要 是 上的凸函数,且在有限集合 上定义的函数 也是该离散集合上的凸函数。这是因为延拓之后的函数 可以看作是卷积下确界:

    因此,延拓之前的函数 也是凸函数。

但并不是所有的最小值操作都保持凸性。

反例

设 是凸函数,函数 并不一定是凸函数。

在一些特殊的问题中,尽管动态规划的转移方程可以写作两个凸函数的最小值的形式,且难以转化为卷积下确界的形式,但是价值函数依然能够保持凸性。在实际处理时,通常需要结合打表和猜测找到这类问题的合理的斜率转移方式。

了解了凸函数及其常见变换后,就可以通过具体的问题理解 Slope Trick 优化 DP 的方法。本文的例题大致分为维护拐点和维护斜率两组,用于理解这两种维护方式的常见操作和实施细节。但是,正如前文所强调的那样,维护方式并不是 Slope Trick 的本质,应当根据具体的问题需要选取合适的斜率段维护方式。

维护拐点

这类问题通常出现在需要最小化若干个绝对值的和式的问题中。因为这类问题中,价值函数的斜率的绝对值并不大,因此维护斜率变化的拐点更为方便。

维护拐点是指维护分段线性函数中,斜率发生变化的点。相当于对于每个斜率为 的斜率段 ,只维护其端点信息,而斜率本身不需要格外维护;因此,这类问题斜率每次发生变化时,都应当只变化一个固定的量。比如,如果维护了拐点集 ,就相当于说:区间 内斜率为 ;向左每经过一个拐点,斜率减少一;向右每经过一个拐点,斜率增加一;故而,区间 内,斜率就是 ,区间 内,斜率就是 ,诸如此类。用形式语言表示,函数可以利用斜率拐点写作

它的最小值就是 ,且可以在区间 内任意位置取到。

例题:最小成本递增序列

给定长度为 的序列 ,求严格递增序列 使得 最小,输出最小值和任意一种最优方案 。

模板题:

例题:转移带限制的情形

给定长度为 的序列 ,求序列 使其满足 对所有 都成立,并使得 最小,输出最小值。

模板题:

维护斜率

还有一些问题,维护斜率更为方便。这类问题通常也可以使用 反悔贪心 或模拟费用流的思想解决。费用流模型中,最小费用往往是流量的凸函数,这就为使用 Slope Trick 提供了基础。

例题:股票交易问题

给定 天股票价格序列 (均为正数),初始持股为 ,每天可买入一股、卖出一股或不交易,求 天后最大利润。

模板题:

例题:搬运土石问题

给定长度为 的序列 和 ,分别表示第 个花园已经有的泥土数量和需要的泥土数量(不能多也不能少)。购买一单位泥土放入任意花园价格为 ,从任意花园运走一单位泥土价格为 ,从花园 向花园 运送一单位泥土价格为 。求满足所有花园需求的最小成本。()

模板题:

习题

本文的最后,提供一些各类算法竞赛中出现过的且可以使用 Slope Trick 解决的问题,以供练习。

参考文献与注释

Footnotes

  1. 不同教材对于凸函数的称呼可能不同。 ↩

  2. 也常称为 卷积、 卷积或者 卷积。 ↩