斜率优化 dp
问题一般形式
给定 \(a_{1\sim n},b_{1\sim n},c_{1\sim n},L_{1\sim n},R_{1\sim n}\),求出
或
其中 \(g(i)\) 的计算依赖 \(f_i\) 时需要满足 \(R_i<i\)(否则依赖成环)
需要满足 \(L_i\) 和 \(R_i\) 单调不降
后一种形式显然可以通过将 \(a,g,b\) 取为相反数转化为第一种形式(当然还有其他方法),因此只考虑第一种形式
以下假定 \([L_0,R_0]\) 为一个空区间(取值任意)
转化
可以转化为有 \(n\) 条直线 \(y=b_i\,x+g(i)\),对于每个 \(i\),求出 直线 \(x=c_i\) 与第 \(L_i\) 到第 \(R_i\) 条直线的交点的 \(y\) 坐标的最小值
\(c_i\) 单调不降,\(b_i\) 单调不升
维护一个直线的双端队列,从左到右直线的斜率严格递减,交点 \(x\) 坐标递增(即一个凸包)
若要将直线 \(s_i\) 从右侧加入队列:
- 当队列为空时直接加入并结束
- 否则设最右侧一条直线为 \(s_r\)
- 显然 \(s_i\) 斜率不大于 \(s_r\) 斜率
- 若 \(s_i\) 斜率等于 \(s_r\)
- 若 \(s_i\) 截距较小,则弹出 \(s_r\) 并继续判断下一条直线
- 否则 \(s_i\) 无法加入队列,结束
- 否则 \(s_i\) 斜率小于 \(s_r\)
- 若队列大小为一则直接加入并结束
- 否则设右侧第二条直线为 \(s_l\)
- 若 \(s_i\) 和 \(s_r\) 交点在 \(s_l\) 和 \(s_r\) 交点右侧,则直接加入并结束
- 否则弹出 \(s_r\) 并继续判断下一条直线
先将 \((R_{i-1},R_i]\) 的直线从右侧加入队列,然后从左侧弹出直线(编号小于 \(L_i\) 时弹出;当队列大小不小于 \(2\) 且 最左侧两条直线交点的横坐标小于 \(c_i\) 时弹出)直到不满足要求(显然 \([L_i,R_i]\) 不为空时不会弹空)
此时从队首转移一定最优
总时间复杂度 \(O(n)\),计算交点时需要注意精度问题(不建议使用浮点数,下同)
\(b_i\) 单调不升
在 \(L_i,R_i,c_i\) 单调不降,\(b_i\) 单调不升 的基础上,删去左侧弹出直线时的第二种条件,转移改为“在队列上二分出第一条 与下一条直线的交点的横坐标(不存在则为 \(\infty\))大于 \(c_i\) 的直线,由其转移过来”
总时间复杂度 \(O(n\log n)\)
\(L_i=1\)
可以使用 std::set 维护凸包,时间复杂度 \(O(n\log n)\),空间复杂度 \(O(n)\)。细节较多
也可以使用李超树,时间复杂度 \(O(n\log \max c_i)\),\(\max c_i=O(n)\) 时空间复杂度 \(O(n)\),否则空间复杂度 \(O(n\log \max c_i)\)(动态开点)。实现相对容易
或者使用 \(\operatorname{CDQ}\) 分治,转化为求出 \([l,M]\) 内的 \(g(i)\) 对 \((M,r]\) 的 \(f_i\) 的贡献:先求出 \([l,M]\) 的凸包,然后将 \((M,r]\) 内的 \(f_i\) 按 \(c_i\) 排序(可每次归并两个子区间将这部分做到线性),转化为 \(c_i\) 单调不降,\(b_i\) 单调不升 的形式,总时间复杂度 \(O(n\log n)\),空间复杂度 \(O(n)\)

浙公网安备 33010602011771号