算法介绍
使用条件
形如dp[i]=min{A(i)×B(j)+C(i)+D(j)}dp[i]=min\{A(i)\times B(j)+C(i)+D(j)\}dp[i]=min{A(i)×B(j)+C(i)+D(j)}的转移方程可尝试使用斜率优化。
(取maxmaxmax是类似的,本文不赘述)
法一:线性规划
考虑把 dp[i]=A(i)×B(j)+C(i)+D(j)dp[i]=A(i)\times B(j)+C(i)+D(j)dp[i]=A(i)×B(j)+C(i)+D(j) 化成y=kx+by=kx+by=kx+b的形式,其中y,xy,xy,x只与jjj有关,k,bk,bk,b只与iii有关,且 xxx严格单调递增。
则有:
D(j)=−A(i)×B(j)+dp[i]−C(i)D(j)=-A(i)\times B(j)+dp[i]-C(i)D(j)=−A(i)×B(j)+dp[i]−C(i)
其中y=D(j),k=−A(i),x=B(j),b=dp[i]−C(i)y=D(j),k=-A(i),x=B(j),b=dp[i]-C(i)y=D(j),k=−A(i),x=B(j),b=dp[i]−C(i)
在平面直角坐标系上把所有的(xj,yj)(x_j,y_j)(xj,yj)(即(B(j),D(j))(B(j),D(j))(B(j),D(j)))标出来。
要令dp[i]dp[i]dp[i]最小,则要令bbb最小,即是要找到某一点(xj,yj)(x_j,y_j)(xj,yj),使斜率为kkk的直线经过该点时算出来的bbb最小。
发现只有 在给出点的下凸壳上的点可能成为最优决策点。
法二:代数法(数形结合)
设j1,j2j_1,j_2j1,j2 (0≤j1<j2<i)(0\leq j_1<j_2<i)(0≤j1<j2<i) 为dp[i]dp[i]dp[i]的两个决策点,且决策点j2j_2j2优于j1j_1j1,则有:
A(i)×B(j2)+C(i)+D(j2)≤A(i)×B(j1)+C(i)+D(j1)A(i)\times B(j_2)+C(i)+D(j_2)\leq A(i)\times B(j_1)+C(i)+D(j_1)A(i)×B(j2)+C(i)+D(j2)≤A(i)×B(j1)+C(i)+D(j1)
化简式子,使不等号左边只与iii有关,不等号右边只与j1,j2j_1,j_2j1,j2有关:
A(i)≤−D(j2)−D(j1)B(j2)−B(j1)A(i)\leq -\frac{D(j_2)-D(j_1)}{B(j_2)-B(j_1)}A(i)≤−B(j2)−B(j1)D(j2)−D(j1)
−A(i)≥D(j2)−D(j1)B(j2)−B(j1)-A(i)\geq \frac{D(j_2)-D(j_1)}{B(j_2)-B(j_1)}−A(i)≥B(j2)−B(j1)D(j2)−D(j1)
设xj=B(j),yj=D(j)x_j=B(j),y_j=D(j)xj=B(j),yj=D(j),则D(j2)−D(j1)B(j2)−B(j1)\frac{D(j_2)-D(j_1)}{B(j_2)-B(j_1)}B(j2)−B(j1)D(j2)−D(j1)可以看作Pj1(xj1,yj1)P_{j_1}(x_{j_1},y_{j_1})Pj1(xj1,yj1),Pj2(xj2,yj2)P_{j_2}(x_{j_2},y_{j_2})Pj2(xj2,yj2)两点连线的斜率。
也就是说,如果存在两个决策点j1,j2j_1,j_2j1,j2满足0≤j1<j2<i0\leq j_1<j_2<i0≤j1<j2<i,使得Pj1P_{j_1}Pj1,Pj2P_{j_2}Pj2两点连线的斜率≤−A(i)\leq -A(i)≤−A(i),那么决策点j2j_2j2优于j1j_1j1。
于是,对于这样子的三个点,可证BBB一定不是最优决策点。
我们可以将BBB从候选决策点中踢出去(删除),只留下AAA和CCC,删后的情况如下图所示:
最终,我们维护的候选决策点应该构成了一个下凸壳:
实现
按xjx_jxj的单调性分类
- xjx_jxj递增(递减的话给xxx取个负就是递增了):单调队列维护凸包
- xjx_jxj不单调:平衡树维护凸包/cdq分治提供单调性
按kik_iki的单调性分类
- kik_iki递增: 只需要维护部分凸包。即用单调队列维护凸包时可以不断弹掉队首,删掉不需要维护的凸包;查询答案时直接取队首即可。
- kik_iki不递增:必须维护完整凸包。即用单调队列维护凸包时不能再弹掉队首了(即用单调栈维护凸包);查询答案时二分找最优决策点。
实现细节
- 当 xjx_jxj 非严格递增时,在求斜率时可能会出现 xj1=xj2x_{j1}=x_{j2}xj1=xj2 的情况,此时最好写成这样的形式:
return Y(j)>=Y(i)?inf:-inf
,而不要直接返回 infinfinf 或者 −inf−inf−inf - 手写队列的初始化是
h=1,t=0
,由于队列初始化大多都要塞入一个点 P(0)P(0)P(0),所以在一些题解中可以看到h=t=1
- 手写队列判断不为空的条件是
h<=t
,而出入队判断都需要有至少 2 两个元素才能进行操作。所以应是h<t
。 - 计算斜率可能会因为向下取整而出现误差,所以 slopeslopeslope 函数最好设为 longdoublelong doublelongdouble 类型。
- 在比较两个斜率时,尽量写上等于,即
<=
和>=
而不是<
和>
。这样写对于去重有奇效(有重点时会导致斜率分母出锅),但不要以为这样就可以完全去重,因为要考虑的情况可能会非常复杂,所以还是推荐加上 1. 中提到的特判,确保万无一失。