斜率优化动态规划 学习笔记

首先看这样一个问题:

洛谷 P3195 [HNOI2008]玩具装箱
题目大意:
n 个物品排成一行,第 i 个物品权值为 Ci ,现要求将这些物品分成若干段,每段的花费为 ((i=lrCi)L)2 (其中 l,r 为这一段的左右端点, L 为给定常数),问最小的总花费.保证 1n5×1041L1071Ci107 .

这题显然是一道 DP 题. 令 dpi 为考虑前 i 个物品的最小代价, sumi=j=1iCj , 可以想出这样的状态转移方程:

dpi=min0j<i{dpj+(sumisumjL)2}

然而, 如果暴力转移时间复杂度是 Θ(n2) 的, 显然会T, 有什么办法优化吗?

动态规划优化的一个重要思路是把可能决策的范围缩小 (即排除不可能的决策) . 顺着这个思路试试看?

首先我们观察状态转移方程, 发现这个 min 函数很碍事, 把它去掉:

dpi=dpj+(sumisumjL)2

这个方程太长了, 所以考虑令 ai=sumiL , bi=sumi , 代入得:

dpi=dpj+(aibj)2

这个平方也很碍事, 把它展开:

dpi=dpj+ai22aibj+bj2

把跟 i 有关的移到等号一边, 跟 j (读作"勾") 有关的放到另一边:

2aibj+dpiai2=dpj+bj2

......感觉直到现在我们做的都是一些很常规的操作...... 但是如果我们放一个直线的表达式跟它对比一下?

(2ai)bj+(dpiai2)=(dpj+bj2)
kx+b=y

于是我们惊奇地发现, 状态转移方程竟然可以看作一条斜率为 2ai , 截距为 dpiai2 的直线和一个在 (bj,dpj+bj2) 的点(我管它叫决策点)!

所以, "转移" 这个过程就可以理解为找到一个斜率为 2ai 的直线交于一个已有的决策点.

其中, 红色直线表示 dpi 对应的直线,绿色直线表示一个可能的 dpj 对应的直线, 点 A 表示这个 dpj 对应的决策点, 点 B 表示 dpi 对应的决策点.

所以我们怎么找一条最优的直线, 使得 dpi 最小? 难道要枚举 j 吗?

当然不用. 因为 dpiai2 只与 i 有关, 所以只要这条直线的截距最小, dpi 就是最小的. 我们假设平面上已经有一堆可供转移的决策点和直线:

可以想象 dpi 对应的直线从下往上移动 (别忘了这条直线的斜率不变) , 显然它第一个接触到的点就是最优决策点.

如果您学过计算几何 (我没学过QAQ) , 您可以马上发现潜在的最优决策点都在这一堆点的下凸包上!

所以我们只需要用一个单调队列来维护这个凸包就可以了.

具体怎么维护等到以后再写罢, 博主累了.

综上所述, 我们通过发掘状态转移方程的性质做到了摊还 Θ(1) 的转移, 非常优秀.

代码:

#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
#define int ll
const int MAXN=50000;
int n,L,c[MAXN+5],sum[MAXN+5],dp[MAXN+5];
deque<int> q;
int a(int i){return sum[i]+i;}
int b(int i){return sum[i]+i+L+1;}
int X(int i){return b(i);}
int Y(int i){return (dp[i])+(b(i)*b(i));}
double slope(int i,int j){return double(Y(i)-Y(j))/double(X(i)-X(j));}
signed main(){
    ios::sync_with_stdio(false);
    cin>>n>>L;
    for(int i=1;i<=n;i++){
        cin>>c[i];sum[i]=sum[i-1]+c[i];
    }
    q.push_back(0);
    for(int i=1;i<=n;i++){
        double qwq=114514.1919810;
        if(q.size()>=2)qwq=slope(q[0],q[1]);
        while(q.size()>=2&&slope(q[0],q[1])<double(2*a(i))){
            q.pop_front();
        }
        int j=q.front();
        dp[i]=dp[j]+(a(i)-b(j))*(a(i)-b(j));
        while(q.size()>=2&&slope(q[q.size()-1],q[q.size()-2])>slope(q[q.size()-2],i))q.pop_back();
        q.push_back(i);
    }
    cout<<dp[n]<<endl;
    return 0;
}
posted @   ztx-  阅读(97)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示