斜率优化
斜率优化
例题引入 & 概念
[[HNOI2008] 玩具装箱]([P3195 HNOI2008] 玩具装箱 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
朴素 DP
令 \(f_i\) 表示枚举到第 \(i\) 个物品, 分成若干段的最小代价.
设 \(\textrm{sum}\) 数组为前缀和数组, 那么就有如下转移方程:
时间复杂度 \(\mathcal{O}(n^2)\), 无法通过.
优化
为了简便, 我们钦定 \(L = L + 1, pre_i = sum_i + i\), 撤去 \(\min\), 那么转移式子就变成了 $f_i = (pre_i - pre_j - L)^2 $.
将平方拆开, 移项, 将含有相同项的合并起来:
考虑一次函数的截距式 \(y = kx + b \Rightarrow b = y - kx\), 将与 \(j\) 有关的信息转化成 \(y\) 的形式, 同时含有 \(i, j\) 的式子转化成 \(kx\), 将需要最小化的 (也就是 \(i\) 有关的) 表示为 \(b\). 具体来说, 我们令:
那么转移方程就转化为: \(y_j = k_i x_j -b_i\), 将 \((x_j, y_j)\) 看作二维平面上的点, 那么 \(k_i\) 表示斜率, \(b_j\) 表示截距.
现在的问题就转化成: 选择合适的 \(j\), 使得 \(b_i\) 最小.
根据下凸壳的性质, 如果线段 ab 的斜率 \(< k\), 线段 bc 的斜率 \(> k\), 那么点 b 就是使得截距最小的最优点.
这样, 当 \(k, x\) 均满足单调性时, 我们可以使用单调队列来维护这个凸壳.
具体来说, 在队列中我们维护一个下凸壳, 即每两个连续点所连成的直线, 其斜率是单调上升的. 当新的点进入队列时, 确保它能够与队列中的点形成凸壳.
- 如图, 队列尾部的点是 3 和 4, 准备进入队列的新点是 5. 比较点 3, 4, 5, 看线段 34 和线段 45 的斜率是否递增. 若递增, 那么 5 入队即可. 否则从队尾弹出 4, 继续比较队尾两个元素直到 5 能够入队为止.
- 出队列, 找到最优点. 设队头的两个点为 \(p_1, p_2\), 如果 \(k_{p_1 p_2} < k\), 说明 \(p_1\) 不是最优点, 弹出. 循环往复, 直到斜率 \(> k\) 为止, 此时队头元素就是最优点 \(p^{\prime}\).
根据上述操作, 我们求解单个 \(f_i\) 是 \(\mathcal{O}(n)\) 的, 但是求解所有 \(f_i\) 还是 \(\mathcal{O}(n^2)\) 的.
事实上, \(k_i\) 是具备单调性的, 所以我们只需要用一个单调队列处理即可. 因为每一个点最多只会出队进队一次, 时间复杂度 \(\mathcal{O}(n)\).
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现