少女祈祷中...

一种 dp 优化

前置知识

单调队列优化 dp,计算几何基础知识,小学数学。

斜率优化

在 dp 中,我们常常能遇到一类转移方程,其中含有 ii 相关的项与 jj 相关的项的乘积,形如这种形式:

fi=maxj<i{fj+aibj}f_i=\max_{j<i}\{ f_j+a_ib_j\}

我们常见的单调队列优化只能优化只含 jj 项的方程,对于这种方程无能为力。因此我们需要利用斜率优化。

先来看一道例题:


任务安排

nn 个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 nn 个任务被分成若干批,每批包含相邻的若干任务。

从零时刻开始,这些任务被分批加工,第 ii 个任务单独完成所需的时间为 tit_i。在每批任务开始前,机器需要启动时间 ss,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。

每个任务的费用是它的完成时刻乘以一个费用系数 cic_i。请确定一个分组方案,使得总费用最小。

数据范围:n5000,ti,ci>0n\le 5000, t_i, c_i>0

加强版:n100000,ti,ci>0n\le 100000,t_i, c_i>0


先设 sumT[i]sumT[i]tit_i 的前缀和,sumC[i]sumC[i]cic_i 的前缀和。

我们可以很容易设计出一个 O(n3)O(n^3) 的转移方程:设 f[i][j]f[i][j] 为前 ii 个任务共分为 jj 段的最小花费。我们就可以列出转移:

f[i][j]=mink<i{f[k][j1]+(sumT[i]+j×s)×(sumC[i]sumC[k])}f[i][j]=\min_{k<i}\{ f[k][j - 1]+(sumT[i]+j\times s)\times(sumC[i]-sumC[k])\}

因为状态数为 O(n2)O(n^2) 级别,无论如何优化都无法通过加强版

因为时间复杂度太高,我们考虑重新设计状态。设 f[i]f[i] 为前 ii 个任务分为若干段的最小花费,则

f[i]=minj<i{f[j]+sumT[i]×(sumC[i]sumC[j])+s×(sumC[n]sumC[j])}f[i]=\min _{j<i}\{f[j]+sumT[i]\times (sumC[i]-sumC[j])+s\times (sumC[n]-sumC[j]) \}

这个方程运用了一个费用提前计算的思想,最后一项 s×(sumC[n]sumC[j])s\times (sumC[n]-sumC[j]) 表示虽然不知道后面分成了多少段,但是知道当前分这一段会对后面这些项造成这么多影响。这个方程的复杂度为 O(n2)O(n^2),可以通过原版数据范围。

为了通过加强版,我们化简一下式子。

f[i]=minj<i{f[j]+sumT[i]×(sumC[i]sumC[j])+s×(sumC[n]sumC[j])}=minj<i{f[j]+sumT[i]×sumC[i]sumT[i]×sumC[j]+s×sumC[n]s×sumC[j])}=minj<i{f[j](sumT[i]+s)×sumC[j])}+sumT[i]×sumC[i]+s×sumC[n]\begin{aligned} f[i] & = \min _{j<i}\{f[j]+sumT[i]\times (sumC[i]-sumC[j])+s\times (sumC[n]-sumC[j]) \}\\ &=\min _{j<i}\{f[j]+sumT[i]\times sumC[i]-sumT[i]\times sumC[j]+s\times sumC[n]-s\times sumC[j]) \}\\ &=\min _{j<i}\{f[j]-(sumT[i]+s)\times sumC[j]) \}+sumT[i]\times sumC[i]+s\times sumC[n] \end{aligned}

我们移一下项,变为:

f[i]sumT[i]×sumC[i]s×sumC[n]=minj<i{f[j](sumT[i]+s)×sumC[j])}f[i]-sumT[i]\times sumC[i]-s\times sumC[n]=\min _{j<i}\{f[j]-(sumT[i]+s)\times sumC[j]) \}

这时就无法化简了,我们考虑下有什么性质。

我们可以设 xj=sumC[j],yj=f[j],ki=(sumT[i]+s),bi=f[i]sumT[i]×sumC[i]s×sumC[n]x_j=sumC[j], y_j=f[j], k_i=(sumT[i]+s), b_i=f[i]-sumT[i]\times sumC[i]-s\times sumC[n]。(注意下标)这时式子变为:

bi=minj<i{yjki×xj}b_i=\min _{j<i}\{y_j-k_i\times x_j \}

这就变成了一个一次函数求截距的式子。注意到 sumT[i]×sumC[i]s×sumC[n]sumT[i]\times sumC[i]-s\times sumC[n]ii 固定时为定值,目前要求 f[i]f[i] 最小值,则问题转化为求截距的最小值。

我们可以把问题放到坐标系中来看:

由于 ii 固定,则斜率 kik_i 固定。我们就可以把问题想象成拿着一条斜率为 kik_i 的直线从下往上平移,第一个到达的点(点的坐标为上面的 (xj,yj)(x_j, y_j))则为最佳转移点(这时截距最小)。我们再考虑下什么样的点才能成为最佳转移点。

考虑三个决策点 i,j,k(i<j<k)i, j, k(i<j<k)

ki,j>kj,kk_{i, j}>k_{j, k} 时,如图:

显然,这时 jj 不可能成为最佳转移点。

而当 ki,j<kj,kk_{i, j} < k_{j, k} 时:

这时 jj 可能成为最佳转移点。

因此,对于所有可能成为最佳转移点的点构成的点集,其斜率必然单调递增。这就是一个凸壳。我们只需要维护这个凸壳,然后查找第一个相交的点即可。(第一个相交的点 ii 满足 ki1,i<kki,i+1k_{i-1,i}<k\le k_{i, i+1}

现在问题变为了:如何维护这个凸壳?

这个问题很简单。只需要用一个单调队列,用类似找二维凸包的方式向外弹出斜率更大的队尾即可。

而题目中还有一个很好的条件:ti>0t_i>0。因此 sumTsumT 单调递增。我们在寻找相交的点时就可以不断从队头弹出无用决策(斜率小于当前的 kk)。

这样就完成了,时间复杂度 O(n)O(n)

加加强版

题意相同。

数据范围:n100000,512ti512,0<ci512n\le 100000,-512\le t_i \le 512, 0<c_i\le 512


这时 tit_i 不满足单调递增的关系,我们无法每次从队头弹出元素,因为弹出的元素可能会成为以后的最佳转移点。因此我们需要维护整个凸包,在转移时,由于凸包上斜率单调递增,我们可以在单调队列里二分出第一个满足 ki1,i<kki,i+1k_{i-1,i}<k\le k_{i, i+1} 的凸包上的点,它就是最佳转移点。时间复杂度会多一个 logn\log n

加加加强版

ti,cit_i, c_i 都可能为负数。


这时横坐标和斜率都无法满足单调递增关系,这就意味着我们无法用单调队列动态维护凸包(可能会在中间插入)。这时常见的有三种解决方案:

  1. 平衡树

平衡树可以做到在数列中动态加入数,这时我们可以在平衡树上二分查找最佳转移点。十分好理解,但是难写。

  1. CDQ 分治

我们设 CDQ(l,r)\mathrm{CDQ}(l, r) 为计算 [l,r][l, r] 这段区间的 ff 值。我们先递归计算 CDQ(l,mid)\mathrm{CDQ}(l, mid)。这时假设我们已经计算完了左半边,考虑左半边对右半边的贡献。我们可以将 [l,mid][l, mid] 的点组成的凸包建出来,因为这一部分已经计算完毕了,所以我们可以直接排序建凸包。

而对于右区间 [mid+1,r][mid + 1,r],我们也可以进行按斜率排序处理,这样就可以按照斜率单调递增的做法来弹出队头转移。最后再递归处理右区间 CDQ(mid+1,r)\mathrm{CDQ}(mid + 1, r)

注意:处理右区间时不要将右区间的点加入凸包,因为我们计算的只是左区间对右区间的贡献,其余的贡献会继续递归处理。

  1. 李超线段树

我们可以再把转移方程搬过来:

f[i]sumT[i]×sumC[i]s×sumC[n]=minj<i{f[j](sumT[i]+s)×sumC[j])}f[i]-sumT[i]\times sumC[i]-s\times sumC[n]=\min _{j<i}\{f[j]-(sumT[i]+s)\times sumC[j]) \}

这时我们不按照上面的设法,我们重新理解下这个式子。

我们可以理解为有 i1i-1 条直线 1i11\sim i-1,每条直线的斜率为 sumC[j]-sumC[j],截距为 f[j]f[j],而我们要查询当 x=sumT[i]+sx=sumT[i]+s 时的最值。这个可以直接用李超线段树维护。

习题

Cats Transport

mm 只猫子,pp 只铲屎官。有 nn 座山,第 ii 座山与第 i1i-1 座山的距离为 DiD_i。这些猫子都去玩,猫子 ii 去第 HiH_i 座山玩,在 TiT_i 时间结束游玩,然后在 HiH_i 傻等铲屎官来接它。铲屎官必须把所有猫子都带上,每个铲屎官以每秒 11 的速度不间断的从 H1H_1 走到 HnH_n。每个铲屎官可以带上无限的猫子。现在安排每个铲屎官的出发时间(可以为负数),最小化猫子们的等待时间之和。求出最小等待时间之和。

数据范围:m100000,p100m\le 100000, p\le 100


我们先设 Ai=Tii=2HiDiA_i=T_i-\sum_{i=2}^{H_i}D_i,即猫子能被接到的最早出发时间。因此如果铲屎官从 tt 时刻出发,则这只猫子等待时间则为 tAit-A_i

我们将 AiA_i 排序,设 ss 为其排好序后的前缀和。利用临项交换法可以证明,一个铲屎官出发后带走的猫子在 AA 中一定是连续的一段。这时我们就可以把问题转化为上面的任务安排。

我们考虑一只铲屎官带走 [l,r][l, r] 这些猫的代价为:

i=lr(ArAi)=(rl+1)Arsr+sl1\sum\limits _{i=l}^{r}(A_r-A_i)=(r-l+1)A_r-s_r+s_{l-1}

我们设 f[i][j]f[i][j] 为前 ii 只铲屎官带走了前 jj 只猫子,则转移方程为:

f[i][j]=mink<j{f[i1][k]+(jk)Ajsj+sk}f[i][j]=\min_{k<j}\{f[i-1][k]+(j-k)A_j-s_j+s_k \}

这样复杂度为 O(m2p)O(m^2p) 的,考虑斜率优化。

我们化简式子,能得到 f[i][j]+sjj×Aj=mink<j{f[i1][k]+skAj×k}f[i][j]+s_j-j\times A_j=\min_{k<j}\{f[i-1][k]+s_k-A_j\times k\}。这样就可以直接斜率优化了。

另外,本题可以加强数据到 p100000p\le 100000。这时我们可以用 wqs 二分来消去 ii 这一维,使得时间复杂度降为 O(mlogp)O(m\log p)。这里不再介绍。

Building Bridges

nn 根柱子依次排列,每根柱子都有一个高度。第 ii 根柱子的高度为 hih_i

现在想要建造若干座桥,如果一座桥架在第 ii 根柱子和第 jj 根柱子之间,那么需要 (hihj)2(h_i-h_j)^2​​ 的代价。

在造桥前,所有用不到的柱子都会被拆除,因为他们会干扰造桥进程。第 ii 根柱子被拆除的代价为 wiw_i,注意 wiw_i 不一定非负,因为可能政府希望拆除某些柱子。

现在政府想要知道,通过桥梁把第 11 根柱子和第 nn 根柱子连接的最小代价。注意桥梁不能在端点以外的任何地方相交。

数据范围:2n105;0hi,wi1062\le n\le 10^5;0\le h_i,\vert w_i\vert\le 10^6


本题很容易设出状态并写出转移方程:fif_i 表示 1i1\sim i 建桥的代价,则

fi=minj<i{fj+(hihj)2+sumi1sumj}=minj<i{fjsumj+hj22hi×hj}+sumi1+hi2\begin{aligned} f_i & = \min _{j<i}\{f_j+(h_i-h_j)^2+sum_{i-1}-sum_{j} \}\\ &=\min _{j<i}\{f_j-sum_{j}+h_j^2-2h_i\times h_j \}+sum_{i-1}+h_i^2 \end{aligned}

这样就化为了斜率优化的形式,由于本题横坐标和斜率都不单调,则需要李超线段树或者 cdq 分治来优化。

The End