斜率优化

Tuning the sound of infinite. 这一次,星星和月亮一起闪耀。


斜率优化

概括地说,就是优化与乘积式有关的 dp(不一定非得递推,但要有满足要求的式子)的小 trick。

省流:朴素 dp \(\to\) 推式子 \(\to\) 找斜率的单调性(画三角形)\(\to\) 单调数据结构维护。前提是要有一些必要的单调性。

天依宝宝可爱!


洛谷 P2900

首先被别人二维偏序的元素一定没什么用,先单调队列处理掉。然后就变成了一个 \(a_i\) 严格递增,\(b_i\) 严格递减的数组。

考虑朴素 dp,设 \(f_i\) 为只考虑前 \(i\) 个且 \(i\) 必选的最小费用,那么有转移:

\[f_i = \max _{1 \le j \le i-1} \{ f_{j-1} + a_ib_j \} \]

注意到有乘积式,所以考虑斜率优化,考虑从 \(j\) 转移比从 \(k\) 转移更优(\(j<k\))当且仅当(推柿子):

\[\begin{aligned} f_{j-1} + a_ib_j & \le f_{k-1} + a_ib_k \\ f_{j-1} - f_{k-1} & \le a_i (b_k - b_j) \\ \end{aligned} \]

由于 \(j<k\),故 \(b_j > b_k\),所以:

\[\begin{aligned} \frac {f_{j-1} - f_{k-1}} {b_k - b_j} & \ge a_i \\ \frac {\color{#66CCFF} f_{j-1} - f_{k-1}} {\color{#EEAADD} b_j - b_k} & \le -a_i \end{aligned} \]

注意到这个很像直线斜率的式子,若将 \(b_j\) 当做 \(x\) 坐标,\(f_{j-1}\) 当做 \(y\) 坐标,那么一个 dp 状态就是坐标系上的一个点。于是 \(j\)\(k\) 优当且仅当这两点间的斜率 \(\le -a_i\)

这是两个点的情况,那么如果是三个点呢?

首先可以证明,若按照 \(i\) 从小到大,三个点编号为 \(1,2,3\),那么点 \(2\) 一定在 \(\overline {1,3}\) 的一侧,所以考虑分讨这两种情况:

  • 若构成上三角,即 \(2\)\(\overline {1,3}\) 的右侧,如图:

    显然,图中直线的斜率 \(k(1,2) < k(1,3) < k(2,3)\)

    因为 \(a_i\) 是递增的,所以 \(-a_i\) 是递减的,也就是说斜率需要 \(\le\) 的数越来越小,所以这三条直线的斜率由满足条件\(\le -a_i\)到不满足是有先后次序的,就相当于这三个的「\(j\)\(k\) 优」到「\(j\) 不比 \(k\) 优」是有序的。那么下面用大于号表示 \(j\)\(k\) 优,分讨:

    1. 初始情况,当 \(k(2,3) \le -a_i\) 时,三条直线的斜率都满足条件;于是 \(1>2,1>3,2>3\),最优的是 \(\color{#66CCFF} 1\)
    2. \(k(1,3) \le -a_i < k(2,3)\) 时,\(\overline {2,3}\) 的斜率不满足条件了;于是 \(1>2,1>3,3>2\),最优的是 \(\color{#66CCFF} 1\)
    3. \(k(1,2) \le -a_i < k(1,3)\) 时,\(\overline {1,3}\) 的斜率不满足条件了;于是 \(1>2,3>1,3>2\),最优的是 \(\color{#66CCFF} 3\)
    4. 最终情况,当 \(-a_i < k(1,2)\) 时,三条直线的斜率都不满足条件;于是 \(2>1,3>1,3>2\),最优的是 \(\color{#66CCFF} 3\)

    我们发现 \(2\) 这个点自始至终没用过,所以可以直接扔掉不管!

  • 然后考虑下三角,如图:

    对应的斜率 \(k(2,3) < k(1,3) < k(1,2)\)

    还是分讨:

    1. 初始情况,当 \(k(1,2) \le -a_i\) 时,三条直线的斜率都满足条件;于是 \(2>3,1>3,1>2\),最优的是 \(\color{#66CCFF} 1\)
    2. \(k(1,3) \le -a_i < k(1,2)\) 时,\(\overline {1,2}\) 的斜率不满足条件了;于是 \(2>3,1>3,2>1\),最优的是 \(\color{#66CCFF} 2\)
    3. \(k(2,3) \le -a_i < k(1,3)\) 时,\(\overline {1,3}\) 的斜率不满足条件了;于是 \(2>3,3>1,2>1\),最优的是 \(\color{#66CCFF} 2\)
    4. 最终情况,当 \(-a_i < k(2,3)\) 时,三条直线的斜率都不满足条件;于是 \(3>2,3>1,2>1\),最优的是 \(\color{#66CCFF} 3\)

    我们发现这时候,三个点就都有用了,所以都需要保留。

紧接着,从三个点推广到多个点,显然遇到任意的上三角都可以删除对应的点 \(2\),那么可以考虑从左到右依次删上三角,最后得到的结果将会是从右到左单增的!如图(蓝线为删除上三角前的,红线为删除之后的):

现在只看红线,考虑如何确定从哪个点转移。首先因为这些红线都没被删除,所以最优的一定是 \(1\);注意到 \(k(4,5) < k(3,4) < k(2,3) < k(1,2)\),且这些斜率从右到左是单减的,而 \(-a_i\) 也是单减的。所以当对于某个 \(i\),某一条直线的斜率不满足条件时,这条线在以后也绝对不会再次满足条件;对应到点上,就是 \(j-1 > j\)(大于号是上面定义的大于号)永远成立了,所以就可以直接把 \(j\) 这个点删掉了!

于是,每次转移时,先把右边不满足条件的点删掉,再用剩下的最右边的点转移即可!

至此已经做完了,接下来就是小学生的会的实现了,拿个单调队列维护即可。

不过有两个地方需要解释。

一是斜率的比较,虽然直接算斜率不是不行,但我们能不用浮点数就不用浮点数。注意到「两点的斜率 \(\le -a_i\) 时,编号小的点更优」这个条件其实是源自从 \(j\) 点还是 \(k\) 点转移,所以直接比较从 \(j,j-1\) 两点中的哪个点转移更优就行了,并不需要真的去计算斜率,斜率只是我们推导和证明过程中使用的工具而已。

二是上三角和下三角的判断,可以用向量叉积来解决。具体地,考虑开始两个图中的那三个点,如果 \(\overrightarrow {1,3} \times \overrightarrow {1,2}\) 的结果为正,则 \(\overrightarrow {1,2}\)\(\overrightarrow {1,3}\) 的逆时针方向,所以构成的是下三角;反之则是上三角。

当然直接判断 \(\overline {1,2}\)\(\overline {1,3}\) 斜率大小也行,不过我不喜欢浮点数qwq!

†关于共线的情况,显然怎么处理都是一样的,因为当直线的斜率相同时,中间的点即使保留,也会在右边的点被删掉的同时被删掉,所以它不会做出任何贡献,所以判断为上下三角都是等效的。

如果不算排序,那么时间复杂度只有 \(\mathcal O(n)\)

submission

天依宝宝可爱!


洛谷 P3648

本质是推式子。

首先注意到当切的位置确定时,顺序不影响答案。简单证明一下,设切出来的三段和分别为 \(a,b,c\),那么先切 \(a,b\) 再切 \(b,c\) 的贡献为 \(a \times (b+c) + b \times c = a \times b + b \times c + c \times a\);可以发现先切 \(b,c\) 再切 \(a,b\) 的结果也是一样的。

根据上面式子还可以发现,假设切的结果中有一段的和为 \(x\),那么其贡献为 \(x \times (\sum a_i - x)\),当然最后要除以 \(2\)

然后设 \(s\)\(a\) 的前缀和数组,则如果切出来的结果有 \((i,j]\) 这一段,那么其贡献为 \((s_j - s_i) \times (s_n - s_j + s_i)\)

dp,设 \(f_{i,j}\) 为考虑前 \(i\) 个,切了 \(j\) 次,\(i\) 后必切的最大贡献,则有转移:

\[f_{i,j} = \max _{j-1 \le k < i} \{ f_{k,j-1} + (s_i - s_k) \times (s_n - s_i + s_k) \} \]

假设 \(x<y\) 且从 \(x\) 转移比 \(y\) 更优,则有:

\[f_{x,j-1} + (s_i - s_x) \times (s_n - s_i + s_x) \ge f_{y,j-1} + (s_i - s_y) \times (s_n - s_i + s_y) \]

可得:

\[\frac {(f_{x,j-1} - s_x^2) - (f_{y,j-1} - s_y^2)} {s_x - s_y} \le s_n - 2s_i \]

于是以 \(s_i\)\(x\) 坐标,\(f_{i,j-1} - s_i^2\)\(y\) 坐标。注意到 \(s_i\) 是递增的,\(s_n - 2s_i\) 是递减的,画一下三角形可以发现要删的是下三角,于是就做完了。

别忘了除以 \(2\)!> <

然后还有一点,叉积为 \(0\) 也要删除,虽然我不知道为什么,但是以后如果遇到还是这么写吧qaq,能写 >= 的就不写 >

submission

天依宝宝可爱!


洛谷 P3195

简单题。

首先朴素的 dp。设 \(f_i\) 为只考虑前 \(i\) 个的最小代价,则有转移(\(s\)\(a\) 的前缀和数组):

\[f_i = \min _{0 \le j < i} \{ f_j + (i - (j + 1) + s_i - s_j - L)^2 \} \]

但是这个式子太长了,虽然没什么影响吧,但我懒得推这么长的式子,所以可以想到 \(s_i \gets s_i + i\)\(L \gets L + 1\),那么原式可以转化为:

\[f_i = \min _{0 \le j < i} \{ f_j + (s_i - s_j - L)^2 \} \]

好看了不少。

\(j<k\) 且从 \(j\) 转移比 \(k\) 优,则有:

\[f_j + (s_i - s_j - L)^2 \le f_k + (s_i - s_k - L)^2 \]

可得:

\[\frac {(f_j + s_j^2) - (f_k + s_k^2)} {s_j - s_k} \ge 2(s_i - L) \]

于是 \(x\) 坐标为 \(s_i\)\(y\) 坐标为 \(f_i + s_i^2\)。因为 \(s_i\) 递增,\(2(s_i - L)\) 递增,于是删上三角。

然后删三角形的时候注意先删三角形再把 \(i\) 入队。

顺便吧斜率优化搞成一个函数了,以后可以当板子使了(喜

submission

天依宝宝可爱!


洛谷 P4360

原来非 dp 也可以斜率优化(?

不过还是有个比较像 dp 的形式的,只要最后能推出来斜率的式子就可以!

首先显然可以设 \(f_{i,j \in \{ 0,1,2 \}}\) 表示后 \(i\) 个位置建造了 \(j\) 个锯木厂的最小代价

虽然这样也挺容易,不过我们有一个更好的策略。注意到最多只能建造 \(2\) 个锯木厂,所以可以简化掉,直接设 \(f_i\) 为原 \(f_{i,2}\),那么就确定了第二个锯木厂的位置,然后枚举第一个的位置就行了。

\(sd\)\(d\) 的后缀和,\(sw\)\(w\) 的前缀和,\(sum\) 为不建锯木厂的代价。考虑新建造的锯木厂能减少的代价,有式子:

\[f_i = \min _{i < j \le n} \{ sum - sd_i \times sw_i - sd_j \times (sw_j - sw_i) \} \]

套路地得到 \(j<k\)\(j\) 优于 \(k\) 当且仅当:

\[\frac {sd_jsw_j - sd_ksw_k} {sd_j - sd_k} \ge sw_i \]

做完了。

submission

天依宝宝可爱!


洛谷 P3628

板子。

\(f_i\) 为只考虑前 \(i\) 个且在 \(i\) 后必切的最大贡献。\(s\) 为前缀和数组,有式子:

\[f_i = \max _{0 \le j < i} \{ f_j + a(s_i - s_j)^2 + b(s_i - s_j) + c \} \]

若从 \(j\) 转移优于从 \(k\) 转移(\(j < k\)):

\[\frac {(f_j + as_j^2) - (f_k + as_k^2)} {s_j - s_k} \le 2as_i + b \]

\(x = s_i , y = f_i + as_i^2\)。由于 \(x\) 单增,\(2as_i + b\) 单减(\(-5 \le a \le -1\)),所以函数上凸。

双倍经验:SP15648。

submission

天依宝宝可爱!

posted @ 2025-08-19 09:34  little__bug  阅读(16)  评论(0)    收藏  举报