斜率优化 dp

问题一般形式

给定 \(a_{1\sim n},b_{1\sim n},c_{1\sim n},L_{1\sim n},R_{1\sim n}\),求出

\[f_i=a_i+\min_{L_i\le j\le R_i}g(j)+b_jc_i \]

\[f_i=a_i+\max_{L_i\le j\le R_i}g(j)+b_jc_i \]

其中 \(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)\)

posted @ 2025-02-26 17:36  Hstry  阅读(7)  评论(0)    收藏  举报