斜率优化(主观理解)

Posted on 2026-10-15 07:43  暮光落阳  阅读(3)  评论(0)    收藏  举报

斜率优化(主观理解)

参考:斜率优化 - OI Wiki

是一种通过数学关系处理dp的优化。

抽象地说:

设当前状态为 \(i\) ,转移源状态为 \(j\) ,若可将转移式化为 \(f(i)g(j)+h(i)+l(j)=ans_i\) 的形式(函数值仅与主元有关,常数随便放进某个函数即可),那么,对应的:

\[\begin{matrix} f(i)&g(j)&+&h(i)-ans_i&+&l(j)\\ -k&x&+&-b&+&y \end{matrix}=0 \]

从而,所有的 \(j\) 都被分离进了 点 \((x,y)\) 。而 \(i\) 存在于 \(k,b\) 中。

那么,为了使 \(ans_i\) 取得最值,需要使此时的截距 \(b\) 取得最值。在 \(i\) 固定时,\(ans_i\) 仅受 \(j\) 影响,由几何关系会发现,使 \(b\) 取得最值的点 \((x,y)\) 总是位于最外围,也就是一个凸包。

那么,仅需维护这个凸包,并通过斜率 \(k\) 查询最值。

实现上,

  1. 如维护下边缘的凸包,斜率随 \(x\) 单调递增,在加入节点,若有原在凸包上的节点被覆盖入凸包内,应将其删去。
  2. 相对位置的判断是通过比较斜率实现的。

算法流程:

  • 初始化节点,如 \((0,0)\) 。
  • 由当前 \(i\) 计算得出 \(k,b\) 。
  • 用 \(k\) 在凸包上查询得到点 \(p\) ,使得 \(\begin{cases}k_{p,p-1}<k<k_{p+1,p}&下凸包\\k_{p,p-1}>k>k_{p+1,p}&上凸包\end{cases}\)。
  • 由 \(k,p,b\) 计算出 \(ans_i\) ,再由 \(ans_i\) 计算出 \(i\) 所对应的点。
  • 将 \(i\) 加入凸包,若是下凸包,\(\forall p<t<i \land k_{i,p}<k_{t,p}\) 或 \(\forall i<t<p \land k_{i,p}>k_{t,p}\),将 \(t\) 移出凸包。上凸包则斜率比较取反。

如果 \(f(i)\) 等函数有单调性,会有极好的优化。如 \(f(i)\) 单调可线性查询,\(g(j)\) 单调可线性维护凸包。

以及一道模板题 [HNOI2008]玩具装箱

Code
#include <bits/stdc++.h>
#define int long long
using namespace std;

int read(){
    int i;
    scanf("%lld",&i);
    return i;
}

struct Node{
    __int128 x,y;
};

bool cmp(Node n1,Node n2,__int128 k){
    return n2.y-n1.y<k*(n2.x-n1.x);
}

bool cmp(Node n1,Node n2,Node n3){
    return (n2.y-n1.y)*(n3.x-n2.x)<(n3.y-n2.y)*(n2.x-n1.x);
}

Node tb[50010];

int n,L;
int pre[50010];
int key=0,li=0;

signed main(){
    n=read(),L=read()+1;
    for(int i=1;i<=n;i++)pre[i]=read()+pre[i-1];
    tb[0]={L,L*L};
    __int128 ans=0;
    for(__int128 i=1,k,b;i<=n;i++){
        k=2*(pre[i]+i),b=-(pre[i]+i)*(pre[i]+i);
        // printf("%lld %lld ",(int)k,(int)b);
        while(key<li&&cmp(tb[key],tb[key+1],k))key++;
        ans=tb[key].y-k*tb[key].x-b;
        Node now={pre[i]+i+L,(pre[i]+i+L)*(pre[i]+i+L)+ans};
        // printf("%lld %lld %lld\n",(int)now.x,(int)now.y,(int)ans);
        while(li&&!cmp(tb[li-1],tb[li],now))li--;
        tb[++li]=now;
    }
    printf("%lld",(int)ans);
}

时间复杂度\(O(n)\)。