Slope Trick 小记

参考资料:

定义

Slope Trick 是一类具有凸性的最优化 DP 的优化手段,其具有效率高,空间小,易于操作等优点。

对于 Slope Trick 而言,其实现方式较为灵活,取决于题目 凸壳 特点,维护出足以还原整个凸壳/有用部分 的信息,进而根据最优值斜率为零的特点进行操作。

使用 Slope Trick 的 DP (并非全部)有如下特点:

  1. 最优化,有凸性
  2. 是连续的一次分段函数
  3. 其斜率变化量是整数,且总变化量不大

一般使用如下信息维护凸壳(并非绝对,具体分析)

  1. 记录凸壳的起始点(x,y)

  2. 记录斜率变化点x 坐标(仅需 x 坐标,是可重集合,一般存储一个表示斜率变化 1(根据上下凸壳是 1/1)),如 f(x)=|x| 存储 {0,0}

    当斜率变化量相当大的时候也可以维护 (x,Δk) 表示到 x 的时斜率变化了 Δk

可并堆由于可以快速合并两个凸壳是极为常用的数据结构,而平衡树由于其堆性质和优秀的扩展性也较为常用。

两个凸函数相加仍然是凸函数

有一些常见的操作:

  1. 合并两个凸壳

    直接合并起始点,然后合并斜率变化点集合

  2. 找最值

    找到斜率为零的那一段

    有一类常见的方法就是开两个堆 L,R 维护 0 段左右侧的变化点。

  3. 加一次函数

    直接加入斜率变化点(根据一次函数的边界),同时维护起始位置

  4. 平移

    整体打标记

  5. 翻转

    利用平衡树维护,打标记

统计答案:

  1. 还原图像找到对应点坐标
  2. 记录决策点位置还原整个DP过程

平时思考也可以考虑固定点(最左侧最右侧或者最小值点)反推整个图像变化

习题集

这里更多着墨于 Slope Trick 部分,对于 dp 式的得出可能较为简略

CF713C

首先令 aiaii 变为单调不降,显然有 dp:fi,j=minkjfi1,k+|jai|

由于 |jai| 为绝对值函数具有凸性,而 f0 是全零,所以 f 具有凸性。

考虑到 minkjfi1,k 得到的凸壳 {(k,gk)},其实是 fi1 所代表的凸壳删去了最小值右侧部分。

所以可以维护最小值位置 p,并舍弃最小值右侧的凸壳(变为一条斜率为 0 的直线),每次加入 ai 时会多出两个转移点 {ai,ai} 有如下决策:

  1. p<ai 此时 ai 成为新的最小值点(由 x 最大的转折点往前推斜率),所以第二个 ai 应当删去
  2. pai 此时 ai 后面(包括了 p)其斜率会增加 1,这也将导致 p 点真实斜率增大,应当删去 p,同时整个图像(a 后面点)整体向上平移了 pai 的长度(可以看作改变后 p 这个位置也位于新的零段上面,aip 这全部斜率都增加了 1

CF1534G

首先转曼哈顿,这样就变成了坐标差绝对值的和,然后你每次 (x1,y1),(x1,y+1)(x,y),起点 (0,0),这就要求了 x,y 同奇偶。

显然对于 (xi,yi) 你走到了 xi 之后再激活他是不劣的,所以我们按照 x 排序,有如下 dp

fxi,y=|yiy|+min|txi|xixi1fxi1,t

对于后半部分的式子,在凸壳上其实相当于将最小值点左侧向左,右侧向右平移 xixi1 个单位长度,然后将最小值变成一个斜率为零的段。

于是可以利用一个大根堆 L 和一个小根堆 R 维护斜率为零的段的左侧点和右侧点,同时记录全局标记 dl,dr 表示当前平移长度。

每次我们讨论 yi 的位置。

  • yi 位于斜率为零的段上

    左侧斜率变化,右侧斜率变化,L,R 里都插入 yidl,yi+dl(注意懒标记是全局的)

  • yi 位于斜率为负的段上

    这时候 L 里最大的点移动到正段,也就是 R 里加入 L 的最大值,同时后面的图像也向上平移了那么多,增加答案

  • yi 位于斜率为正的段上

    类似上一情况处理即可。

校内模拟赛(13)-蛋糕

很自然地转化相对关系为往右走或者往上走,则可以得到一个 dp 式 fx,y 表示走到了 (x,y) 的最小代价,为了简化边界我们钦定 an+1=0

有转移:

fx,y=min(fx1,y+max(axy,0),fx,y1+sufx+1)

其中 suf 是后缀 max

注意到也可以写作:

fx,y=minky(fx1,k+max(axk,0)ksufx+1)+ysufx+1

首先可以注意到 ksufx+1 是下凸的,max(axk) 是下凸的,初始的 f0,y 是下凸的(f0,y=y·suf1),那么可以假定 gk=fx1,k+max(axk,0)ksufx+1 是一个下凸函数,同时加上 y·sufx+1 这也是个下凸函数,以至于可以归纳有 fx 具有凸性。

感性理解其实挺简单的。

那么可以考虑这个操作在干嘛,先分析 f,g 的关系,相当于是 g 在最小值后推平成了一条直线,然后再把所有线段斜率加上 sufx+1

至于 g 内部在干嘛,相当于是把所有线段斜率减去 sufx+1,然后插入 ax 这个斜率转折点,那么不妨看作先插入 ax,将 [0,ax] 的斜率减去一,接着将全局斜率减去 sufx+1,删去斜率 0 的部分,然后将全局斜率增加 sufx+1(在这个过程中 0 点的值肯定是增加了 ai,因为你相当于是 ai 的位置都有这个函数的复合)

这样的话,其实也可以当作是原本(插入 a) 后,将全局斜率 >sufx+1 的部分删去,替换为 sufx+1 即可。

动态维护当前的 maxk 也就是最右侧斜率即可。

同时我们让 an+1=0,则最终的最右侧凸壳也就是最小值位置,其斜率为零,其坐标(y)显然也是零,因为每个转折点都在其左侧,而我们要求的是 fn+1,0,倒推回去(我们知道最小值位置是 0,用转折点回撤)

APIO2016 烟火表演

容易写出朴素DP式,也就是 fi,j 表示子树 i 所有叶子到 i 距离为 j 的最小代价,有:

fi,j=minkjfi,j+fson,k+|w(i,son)(jk)|

发现绝对值函数是凸的,所以叶子的 f 是凸的,进而这相当于是三个凸包的 (min,+) 卷积,所以 f 是凸的,也即我们归纳证明了 f 是凸的。由于绝对值函数是下凸的,所以 f 是下凸的。

然后我们考虑一个绝对值函数有什么影响。

由于我们关心最小值,所以我们只关心斜率为 0 的段,先看看这一段如何变化。

不难发现假设原本段是 [l,r],变化后等价于 l 的部分向上平移 w[l,r] 向右平移 w,而新的 [l,l+w] 斜率成了负一,后面的斜率全部改成 1

我们只关心最小值,所以我们只需要维护半个凸壳,也就是 k0 的部分。

那么我们可以忽略 k>0 的变化,现在问题变成了如何在合并儿子后删掉后面。

其实是简单的,有 c 个儿子就会有 c1 个斜率正的段,直接删掉即可(增加了 c1 个拐点)。

(我们并没有管它,但是它合并上来后会形成拐点)

发现我们只需要维护凸壳上点的横坐标,维护即可。

也即使用一个可并堆(大根堆删后面),依次合并儿子,然后弹出前面的 c1 个,并再弹出两个作为 [l,r] 整体平移后又插回去,注意到点变化后自然会继承前面的负斜率导致前面一截斜率是 1

这里注意对于叶子节点而言,即使这是一个点也要看成一段,这是 c1 个的依据(段段合并)。

CF280E

有教育意义的 Slope Trick。

首先我们可以通过平移将 [a,b] 转移为 [0,ba],也就是 yiyi1[0.ba],真实 yi=yi+(i1)a。可以也先让 xi=xi(i1)a

那么容易有 fi,j=minjk[0,ba]fi1,k+(xij)2

这里唯一的问题就是第二维太大了,我们考虑维护函数图像。

考虑到 (xij)2 也是凸函数,但是现在它不是一次线性函数了,怎么办?

其实我们仍然可以维护二次分段函数,二次函数的平移与求最值也是能够算出的。

所以我们维护这个二次分段函数。

minjk[0,ba] 这个操作相当于将最值所在位置变成一条长为 ba 的水平线段,后面的图像往后水平平移 ba 个单位长度即可。

这里就相当于原本最值所在的二次函数在最值所在位置分裂成两个,然后插入一条水平线段,可以 O(n2) 暴力维护。

每次找到最值位置,就可以求出 y,再倒推计算贡献就好了

ABC217H

显然建立 B 的笛卡尔树,设 f[i,j] 为树 i 操作后最大值 j 的最小代价。

显然离开子树后子树都是整体操作的

f[i,j]=min(f[i,j1],f[lc,x]+f[rc,y]+max(max(x,y)j,0)×Bi)

显然可以优化为:

j:max0,f[i,j]=min(f[i,j+1]+B[i],f[lc,j]+f[rc,j])j:0max,f[i,j]=min(f[i,j1],f[i,j])

现在我们做到了 O(nv),显然 v 可以离散化得到 O(n2)

换个写法:

f[i,j]=mink=0max(Aij,0)(f[lc,j+k]+f[rc,j+k]+kBi)

sizi=1 时是一条直线

当合并时这个形式感觉由于 B 递增会有斜率递增的凸性

容易发现由于 f 的递减,sizi=1 的时候有凸性,大胆猜测整个函数有凸性。

首先假定成立,则设 s(x)=flc(x)+frc(x) 有凸性且递减,则比较决策对于 jk,k+1,有:

s(k)+kBis(k+1)+(k+1)Bi=s(k)s(k+1)Bi

相当于把 (k,s(k))(k+1,s(k+1)) 之间的斜率与 B 取较小值。

那么在原本的斜率递增的情况下会删除一个尾巴/脑袋,还是递增的。

而且至多有 siz 段不同的斜率,每段最多删一次。

考虑用数据结构维护斜率,然后每次删除增加,我们需要启发式合并?。

不妨维护 (pos,Δy)Δx=1),用可并堆(posmin 的左偏树就行),这里是由于斜率变换量较大,维护每个位置的斜率变化总量

讲讲实现(谁再喷我水):

维护 f0i 表示树 i 凸包的首项,也就是上面的 fi,0

然后维护 (pos,Δy),比如一棵树初始化是 (0,bi),(ai,0),和其两个子树的凸包合起来。插入 (0,bi) 这是因为实现方程里面的 f[i,j]=mink=0max(Aij,0)(f[lc,j+k]+f[rc,j+k]+(j+k)Bi)jBij+k 这一项,而 (ai,0) 是卡住上界。

(可以看作是三个凸包,f[lc],f[rc] 以及 0,Bi,2Bi 的和)

然后我们将斜率小于零的部分以及初始位置小于 ai 的部分全部删去(因为你必须要操作根节点)。注意删完了要加一个新的首项进去。

接着我们插入 (0,bi) ,这是因为实现方程里面的 f[i,j]=mink=0max(Aij,0)(f[lc,j+k]+f[rc,j+k]+(j+k)Bi)jBi

注意到我们只需要维护这些二元组的快速合并和查首项,用可并堆(左偏树)即可。

posted @   spdarkle  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示