问题形式
用来优化一类 dp,其转移方程满足:
$$dp_i=f_i(\max_{j(式子中取 min 也可,与 max 类似,不再赘述)其中 $k_j,b_j$ 是只与 j 相关的数,$x_i$ 是只与 i 相关的数,$f_i$ 是只与 i 有关的函数。
斜率优化就是优化的求 $\max_{j
两种理解
截距法
将每个 j 对应到平面上一点 $(k_j,b_j)$,然后用一个斜率为 $-x_i$ 的直线从上往下移动,直到碰到一个点,此时 y 轴截距即为答案。
函数法
将 $g_j(x)=k_j\times x+b_j$ 看成只与 j 有关的的一次函数,原式等价于在 $x=x_i$ 处所有函数的极值。
感觉这个方法比较好理解,后文都以此方法为例。
如何维护
因为是将所有一次函数取 max,所以所有可能成为答案的直线必定是一个斜率递增的下凸壳,在不同情况下,可采用不同的方法维护凸壳上的函数。
$k_i,x_i$ 都随 i 递增——单调队列
插入直线时,不断弹出队尾,直到队尾的直线在凸壳上没有被新直线完全覆盖,再将新队列加入队尾。
查询时,不断弹出队首,直到队列前两条直线的交点大于 $x_i$,此时队首的函数即为答案。
$k_i$ 随 i 递增,$x_i$ 不随 i 递增——单调队列+二分
插入方法同上,查询时不能直接弹队首了,应该在队列上二分,找到凸壳上包含 $x_i$ 的一段直线。
$k_i,x_i$ 都不随 i 递增——李超树
可以用李超树维护这个凸壳,后文有介绍,(听说也可以用平衡树,CDQ 分治维护,但不太好写)。
简单快捷李超树
李超树是一个支持以 $O(\log n)$ 复杂度插入一条直线(如果是插入线段,则复杂度为 $O(\log^2 n)$),并以同样复杂度查询某一个位置的最大值的线段树。
插入操作
李超树的每个节点上维护的是:在当前区间的中点,函数值最大的函数(下称最优函数),可以如下更新:
- 考虑新插入的函数,与区间的原最优函数:
- 将两者中较优的存到这个节点上
- 用两者中较劣的更新子区间:
- 如果较劣的函数比较优的函数斜率大,则用其递归更新右子区间(如下面图 1 所示,这条线段仍然有可能在右子区间里成为最优函数)
- 否则用其递归更新左子区间(同理)
查询操作
求 x 处最大的函数值,只需将线段树上 x 所在区间,以及它祖先上所有区间的最优函数,在x处的函数值取 max 即可。
题目
AcWing 301. 任务安排2
费用提前计算,记 $t_i,c_i$ 的前缀和为 $st_i,sc_i$,列出转移方程:
$$dp_i=\min_{j其中斜率与横坐标 $-sc_j,st_i$ 都单调,只需单调队列维护即可AcWing 302. 任务安排3
与上一题题意相同,区别在 $st_i$ 不单调,只需在单调队列上二分即可。
代码:
|
|
P3628 [APIO2010]特别行动队
直接推式子,方程为:
$$dp_i=(a s_i^2+b s_i+c) + \max_{j其中斜率与 x 坐标 $-2a s_i,s_i$ 都单调变化,所以单调队列维护即可。代码:
|
|
P3648 [APIO2014]序列分割
发现结果与切割顺序无关,不难推出 dp 式,其中斜率与 x 坐标单调增。
注意特判两条线平行的情况,返回正负无穷,至于具体返回哪个,因题而异。
代码:
|
|
P5017 [NOIP2018 普及组] 摆渡车
dp 转移不难变成斜率优化的形式,可以线性解决。
还可以进一步利用两人到达时间相差超过 $2m$ 可以当成 $2m$ 的性质,缩短时间轴。
复杂度 $O(\min(nm,T))$
代码:
|
|
P4027 [NOI2007] 货币兑换 (李超树)
发现最优情况下每一次买入必定把所有钱都用了,每次卖出都必定把所有金券卖了。
于是不难推出转移方程为($dp_i$ 表示第 i 天,卖币之后,买币之前,最多拥有多少钱):
$$dp_i=max(dp_{i-1},\max(\underbrace{(B_i/A_i)}_{x_i}\times \underbrace{A_i dp_j/(A_j Rate_j+B_j)}_{k_j}+\underbrace{Rate_j A_i dp_j/(A_j Rate_j+B_j)}_{b_j}))$$发现斜率与横坐标都不一定单调,只能用李超树维护。
又因为用李超树查询的横坐标是浮点数,不太好搞,可以提前将会查询到的 x 坐标离散化。
代码(甚短):
|
|