斜率优化 DP
斜率优化 DP 有个很套路化的形式:
这样的形式:
dp[i]=j:conditionmaxdp[j]+f(i)×g(j)求 max 的话,通常是维护点集的上凸壳
或者……
dp[i]=j:conditionmindp[j]+f(i)×g(j)求 min 的话,通常是维护点集的下凸壳
例题
Luogu P5504
首先我们需要想到的一点是:如果取的是 [l,r] 的贝壳,那么选择的 s0=sizel=sizer . 如果不这样做,考虑选取的是 [l,r] 而 l<k<r,sizek=sizer,那么,我们完全可以把这一段拆成 [l,k−1],[k,r] 两段,答案肯定更优。
然后,我们考虑 dp[] 式子,令 dp[] 就是我们想要的最大柠檬数,把它想象成一个总是从前缀转移的 dp,可以列出
c=sizeidpi=j≤i∧sizej=cmax(dpj−1+c×(sumc,i−sumc,j+1)2)
其中 sumc,i 表示 1∼i 里 size 为 c 的贝壳数量。
我们把式子展开成斜率优化的形式,首先拿掉 max:
dpi=dpj−1+c×(sumc,i2+(sumc,j−1)2−2⋅sumc,i⋅(sumc,j−1))由于 sizej=sizei,我们可以适当的替换 c 为 sizej,然后写成 b(i)=y(j)−k(i)⋅x(j) 的形式
b(i)dpi−c⋅sumc,i2=y(j)dpj−1+c⋅(sumc,j−1)2−k(i)(2⋅c⋅sumc,i)⋅x(j)(sumc,j−1)因为我们的 dpi 要求的是 max,而 sumc,j−1 是递增的,所以,我们需要维护的是上凸壳,插入的点的坐标是 (sumc,j−1,dpj−1+c⋅(sumc,j−1)2).
Code
要注意的代码细节:
- 由于 j 其实可以选择 i 自己(即只取长度为 1 的区间),所以我们需要先把 dpi−1 塞进凸壳里,再在凸壳上二分斜率判断(代码 49 行)
- 二分判断斜率的时候,要注意越界问题(即如果 mid=r−1,则此时 mid+1 会造成数组越界).
我这里的话,是在 convex hull 的末尾额外添加了一个点确保不会越界(绿色高亮和红色高亮),我这里的二分是 l + 1 < r
的写法。
且 67 行的 r--
也是为了避免越界。
- 此外如果用 mid 与 mid+1 的点之间的斜率,最后的转移点要取 mid+1,因为 mid ~ mid+1 的斜率 ≥k 而 mid+1 ~ mid+2 之间的斜率 <k.