斜率优化 DP

更新日志 2025/07/02:开工。

前言

其实去年十月写过一篇斜率优化,但好像是在乱扯来着。

所以重写吧。

用处

斜率优化一般用于优化具有一定性质的最优化 DP 问题。

大概会长这样:

\[f_i=\max f_j+a_ia_j+v_j \]

实现

常规实现

我们首先将转移方程转化为 \(y=kx+b\) 的形式。其中 \(x,y\) 仅与 \(j\) 有关,而 \(k,b\) 仅与 \(i\) 有关。

比如上面那个式子就可以转化成:

\[f_j+v_j=a_ia_j-f_i \]

这样的话我们就可以把各个决策点 \(j\) 当作二维平面上的点存起来,横纵坐标就是上面推出的 \(x,y\) 值。

转移时,式子中的 \(k\) 是一定的,而我们想要最大化或最小化那个 \(b\)(视 DP 转移要最大化还是最小化以及 \(b\)\(f_i\) 的正负决定)。

问题变为:平面上有一些点,有一条斜率一定的直线,希望你找到一个点使得这条直线经过该点时 \(b\) 最小。这个点就是最优决策点。

不难发现真正有用的只有这些点的凸壳。如果要求 \(b\) 最大,那么就是上凸壳,否则就是下凸壳。

这里提出,要求各次插入的 \(x\) 具有单调性,否则维护凸壳就会变得困难。如果不具有的话,请使用李超线段树,或者复杂的分治做法。

最好使得 \(x\) 单增——如果不满足就给式子两边都乘上负一即可,这样方便易记。

使用队列维护凸壳,每次向队尾插入节点前,倘若凸壳内存在连线(也就是多于一点),那么判断最后一条连线与新产生的队尾同新决策点的连线的关系,如果不满足单调性弹出即可。

然后是查询,如果 \(k\) 不具有单调性那就只能二分了,或者使用李超线段树!对于 \(k\) 具有单调性的情况,我们最好使得 \(k\) 的单调性与凸壳连线的单调性一致,这样我们就能每次将队头斜率不优的弹出,然后直接取队头作为最优决策点即可。

什么时候不优呢?由于 \(k\) 具有单调性——以单增、下凸壳为例,当队头与队头后一点连线的斜率大于了当前 \(k\),那么说明队头显然没有队头下一位优,且对于以后所有更大的 \(k\) 都是如此,因此队头就没用了,直接弹出即可。

注意特判两点连线 \(x\) 值相同的情况,这时候需要让其斜率满足必然会在比较时使得队头被弹出,具体是 \(+\infty\) 还是 \(-\infty\) 视情况而定。

李超树实现

李超树介绍

李超树实现就简单多了,考虑式子:(下标表示与何有关,由 \(j\) 转移到 \(i\)

\[f_i=k_jx_i+b_i \]

我们对每一个决策点维护一条直线,然后每个待决策点都直接找相应 \(x_i\) 的最大或最小值即可。

同时,李超线段树实现转移区间限制也是简单的。

例题

序列分割

code
int n,k;
ll a[N],s[N];
int hd,tl,Q[N];
ll f[N][K];
int lst[N][K];

inline flt slope(int a,int b,int k){if(s[a]==s[b])return -1e100;return (((flt)s[a]*s[a]-f[a][k-1])-((flt)s[b]*s[b]-f[b][k-1]))/(s[a]-s[b]);}

inline void Main(){
    read(n,k);
    rep(i,1,n)read(a[i]),s[i]=s[i-1]+a[i];
    rep(t,1,k){
        Q[hd=tl=1]=0;
        rep(i,1,n){
            while(hd<tl&&slope(Q[hd],Q[hd+1],t)<=s[i])++hd;
            f[i][t]=f[Q[hd]][t-1]+s[Q[hd]]*(s[i]-s[Q[hd]]);
            lst[i][t]=Q[hd];
            while(hd<tl&&slope(Q[tl-1],Q[tl],t)>=slope(Q[tl],i,t))--tl;
            Q[++tl]=i;
        }
    }
    put(f[n][k]);
    for(int now=lst[n][k],t=k;t;now=lst[now][--t])put(now,' ');
}
posted @ 2025-07-02 14:05  LastKismet  阅读(34)  评论(0)    收藏  举报