斜率优化(Convex Hull Optimisation)

斜率优化(Convex Hull Optimisation)

英文直译为 凸包优化, 指利用状态转移方程的特殊性质, 将决策转化为坐标系中的点, 维护下凸壳进行优化的技巧.

February 2021 清北学堂 DP-图论营, 讲斜率优化的时候我精准睡觉, 从开始睡到结尾, 成为开学后校内测试的眼泪.

问题转化

设有一类 DP 问题, 其转移方程形如

fi=min/max(aibj+fj+ci)

其中, ai, bi, fi 单调

则可以使用斜率优化

将方程转化为函数, 变形成以 bj 为自变量, fj 为因变量的函数.

fj=aibj+fici

这时, 函数图像是一条以 ai 为斜率, fici 为截距的直线, 经过点 (bj,fj), 这个点被称作决策点

由于 ai, ci 只和 i 有关, 所以对于某一个 i, 最佳决策就是使图像截距最小/大的 j, 问题转化成找一个点 (bj,fj), 使经过它的斜率为 ai 的直线截距最小/大

上凸 & 下凸

由于单调性的增减不会影响原理, 所以不失一般性, 我们假设 ai, bi, fi 单增, 且最优决策点使得直线截距最小

这样一来, 就有 Pj+1 一定在 Pj 右上出现. 斜率 kj,j+1>0

分析问题的几何性质可以发现, 对于同一个 i, 由于斜率一定, 所有决策点对应的直线都平行, 所有一个决策点比另一个优, 当且仅当这个点对应的直线在另一条之下; 一条线在另一条之下, 当且仅当这条线在另一条线上的一个点之下

容易发现, 如果 kj1,j>kj,j+1, 则 Pj 一定不是最优决策点, 因为如果 Pj 优于 Pj1, 说明 Pj1 在直线 lj 之上, kj1,j<ai. 又因为 kj1,j>kj,j+1, 所以 ai>kj,j+1, Pj+1 优于 Pj; 同理, 当 Pj 优于 Pj+1 时, 一定有 Pj1 优于 Pj.

我们把上面 Pj 这种情况称作 上凸, Pj 不可能是最优决策, 所以最优决策只能出现在 下凸 的情况, 即 kj1,jkj,j+1

下凸壳

维护一个单调队列, 使得在队列中的点满足 kj1,j<kj,j+1 (从这时开始, 下标 j 特指队列中的下标), 这时候, 相邻点连成的斜率随k不断增大的折线就称作下凸壳. 下凸壳的性质是没有一个点位于其下, 在队列里的点位于下凸壳上, 不在队列里的点位于下凸壳内

容易看出, 对同一个 i, 下凸壳的斜率为 ai 的切线的切点就是最佳决策点. 没有任何决策点在切线之下, 也就没有更优的决策了.

求解

下凸壳上点 Pj 是切点, 必须满足 kj1,jaikj,j+1

由于斜率 ai 也具有单调性, 每次决策时会将其最佳决策点前面的点删掉, 所以凸包总共会被遍历一次, 每个点也只会入队一次, 因此时间复杂度是 O(n)

需要注意的是, 由于新的决策点 Pi 需要入队, 所以必须在入队时保证下凸原则, 入队前删掉队尾的点 Pr, 直到 kr1,rkr,i, 将 Pi 入队

例题Luogu3195

题面大意

n 个物品, 大小为 ci, 可以选取一段连续的物品 (l,r) 装入容器, 容器大小为 sumrsuml1+rl, 大小为 x 的容器花费 (xL)2, L 是常数. 求装下所有物品的最小总费用.

1n5104,1L,Ci107

状态设计

计算前缀和 sumi 表示前 i 件物品总大小

fi 表示前 i 个物品的最小花费, 写出方程

fi=min(fj+(sumisumj+ijL1)2)

去掉取最小值, 整理

fi=fj+(sumi+iL1)22(sumi+iL1)(sumj+j)+(sumj+j)2

ti=sumi+iL1, kj=sumj+j, 则

fi=fj+ti22tikj+kj2

整理得

fj+kj2=2tikj+fiti2

得到以 kj 为自变量, fj+kj2 为因变量的函数. 因为 ai>0, 所以 2ti, kj, fj+kj2 单调递增, 可以使用斜率优化

表示出决策点的坐标

x=kjy=fj+kj2

斜率比较

kj1,j<kj,j+1(yjyj1)(xj+1xj)<(xjxj1)(yj+1yj)

kj1,j<kyjyj1<k(xjxj1)

代码

int main() {
  n = RD();
  L = RD(); 
  sum[0] = 0;
  for (register unsigned i(1); i <= n; ++i) {
    a[i] = RD();
    sum[i] = sum[i - 1] + a[i];
    b[i] = sum[i] + i - L - 1;  //处理 t[i] 
    c[i] = sum[i] + i;          //处理 k[i] 
  }
  f[0] = 0;
  Hull[1].x = 0;
  Hull[1].y = 0;
  Hull[1].ad = 0;   //管他有没有必要的初始化 
  for (register unsigned i(1); i <= n; ++i) {
    while (l < r && (Hull[l + 1].y - Hull[l].y < ((b[i] * (Hull[l + 1].x - Hull[l].x)) << 1))) {
      ++l;  //将左端过气决策点 (斜率过小) 踢出凸壳 
    }
    f[i] = f[Hull[l].ad] + (b[i] - c[Hull[l].ad]) * (b[i] - c[Hull[l].ad]);
    now.x = c[i];
    now.y = c[i] * c[i] + f[i];     //转移 
    while (l < r && ((Hull[r].y - Hull[r - 1].y) * (now.x - Hull[r].x) > (Hull[r].x - Hull[r - 1].x) * (now.y - Hull[r].y))){
      --r;   //入队前维护下凸性 (单调性)
    }
    Hull[++r].x = now.x;
    Hull[r].y = now.y;
    Hull[r].ad = i;   //入队 
  }
  printf("%lld\n", f[n]);
  return Wild_Donkey;
}
posted @   Wild_Donkey  阅读(238)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
历史上的今天:
2020-03-15 数论: 莫比乌斯反演 ( 四 ) 例题
点击右上角即可分享
微信分享提示