斜率优化DP

介绍

形如 f ( i ) = min ⁡ { a i ∗ x ( j ) + b i ∗ y ( j ) } f(i)=\min\{a_i*x(j)+b_i*y(j)\} f(i)=min{aix(j)+biy(j)} DP \text{DP} DP方程一般都可以使用斜率优化。

其中 b b b单调递增, x ( j ) , y ( j ) x(j),y(j) x(j),y(j)都是通过 j j j能在常数时间内确定的值。


斜率优化

考虑 j j j的转移 f ( i ) = a i ∗ x ( j ) + b i ∗ y ( j ) f(i)=a_i*x(j)+b_i*y(j) f(i)=aix(j)+biy(j),以 x x x为横轴, y y y为纵轴,建立平面直角坐标系。

那么 j j j的信息就可以用一个点来表示,原方程变为 f i = min ⁡ { a i x + b i y } f_i=\min\{a_ix+b_iy\} fi=min{aix+biy}

转化为斜截式方程: y = − a b x + f i b y=-\dfrac{a}{b}x+\dfrac{f_i}{b} y=bax+bfi,若 b > 0 b>0 b>0 f i f_i fi最小时, f i b \dfrac{f_i}{b} bfi最小,所以我们需要最小化纵截距。

将斜率为 − a b -\dfrac ab ba的直线向上平移,接触到的第一个点就是最优决策点。

观察可得,所有可能的最优决策点必定在平面点集的凸包上,也就是说不在凸包上的点一定不是最优决策点。所以我们可以将不在凸包的点删去。

在这里插入图片描述

如上图,点D不在凸包上,则将其删去。

那怎么判断这个点是否在凸包上呢。只需看它与前后点的斜率即可。设图中CD边的斜率为 k 1 k1 k1,DE边的斜率为 k 2 k2 k2,若 k 1 > = k 2 k1>=k2 k1>=k2,则D一定不在凸包上。

一般情况下,直线的斜率和题目给的二元组的横坐标都满足单调性。那么我们可以用单调队列维护各点。

对于每个 i i i

  • 判断队头的两点斜率是否大于等于当前所需要的斜率,如果小于,则删除队头元素
  • 用队头元素计算 f i f_i fi的值
  • x ( i ) , y ( i ) x(i),y(i) x(i),y(i)放入队尾,设 i i i前一个点为 t 1 t1 t1 t 1 t1 t1前一个点为 t 2 t2 t2,如果 i i i t 1 t1 t1的斜率小于 t 1 t1 t1 t 2 t2 t2的斜率,则 t 1 t1 t1不在凸包上,将 t 1 t1 t1删去

平摊下来,时间复杂度为 O ( n ) O(n) O(n)

例题

玩具装箱

首先,我们可以求出状态转移式:

f i = min ⁡ j = 0 i − 1 { f j + ( i − j − 1 + ∑ k = j + 1 i c k − L ) 2 } f_i=\min\limits_{j=0}^{i-1}\left\{\begin{matrix}f_j+(i-j-1+\sum\limits_{k=j+1}^{i}c_k-L)^2\end{matrix}\right\} fi=j=0mini1{fj+(ij1+k=j+1ickL)2}

为了计算方便,我们使 L = L + 1 L=L+1 L=L+1

s i = ∑ i = 1 n c i + 1 s_i=\sum\limits_{i=1}^nc_i+1 si=i=1nci+1,最优决策点为 j j j,则:

f i = f j + ( s i − s j − L ) 2 = f j + s i 2 − 2 s i s j − 2 s i L + ( s j + L ) 2 f_i=f_j+(s_i-s_j-L)^2=f_j+s_i^2-2s_is_j-2s_iL+(s_j+L)^2 fi=fj+(sisjL)2=fj+si22sisj2siL+(sj+L)2

对于两个决策点 j , k j,k j,k满足 j > k j>k j>k,若 j j j优于 k k k,则:

f j + s i 2 − 2 s i s j − 2 s i L + ( s j + L ) 2 ≤ f k + s i 2 − 2 s i s k − 2 s i L + ( s k + L ) 2 f_j+s_i^2-2s_is_j-2s_iL+(s_j+L)^2\leq f_k+s_i^2-2s_is_k-2s_iL+(s_k+L)^2 fj+si22sisj2siL+(sj+L)2fk+si22sisk2siL+(sk+L)2

f j − 2 s i s j + ( s j + L ) 2 ≤ f k − 2 s i s k + ( s k + L ) 2 f_j-2s_is_j+(s_j+L)^2\leq f_k-2s_is_k+(s_k+L)^2 fj2sisj+(sj+L)2fk2sisk+(sk+L)2

f j − f k + ( s j + L ) 2 − ( s k + L ) 2 ≤ 2 s i ( s j − s k ) f_j-f_k+(s_j+L)^2-(s_k+L)^2\leq 2s_i(s_j-s_k) fjfk+(sj+L)2(sk+L)22si(sjsk)

f j − f k + ( s j + L ) 2 − ( s k + L ) 2 s j − s k ≤ 2 s i \dfrac{f_j-f_k+(s_j+L)^2-(s_k+L)^2}{s_j-s_k}\leq 2s_i sjskfjfk+(sj+L)2(sk+L)22si

x ( i ) = s i , y ( i ) = f i + ( s i + L ) 2 x(i)=s_i,y(i)=f_i+(s_i+L)^2 x(i)=si,y(i)=fi+(si+L)2,则 y ( j ) − y ( k ) x ( j ) − x ( k ) ≤ 2 s i \dfrac{y(j)-y(k)}{x(j)-x(k)}\leq 2s_i x(j)x(k)y(j)y(k)2si

其中 y ( j ) − y ( k ) x ( j ) − x ( k ) \dfrac{y(j)-y(k)}{x(j)-x(k)} x(j)x(k)y(j)y(k)可看作 j , k j,k j,k两点的斜率。

因为斜率 2 s i 2s_i 2si是递增的,每个点的横坐标 s i s_i si也是递增的,所以我们就可以直接用单调队列做了。

时间复杂度为 O ( n ) O(n) O(n)

code

#include<bits/stdc++.h>
using namespace std;
int n,l,head=1,tail=1,a[50005],h[50005];
long long s[50005],f[50005];
long long gt(int j,int k){
    return f[j]+(s[j]+l)*(s[j]+l)-f[k]-(s[k]+l)*(s[k]+l);
}
int main()
{
    scanf("%d%d",&n,&l);++l;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);s[i]=s[i-1]+a[i]+1;
    }
    for(int i=1;i<=n;i++){
        while(head<tail&&gt(h[head+1],h[head])<=2*s[i]*(s[h[head+1]]-s[h[head]])) ++head;
        f[i]=f[h[head]]+(s[i]-s[h[head]]-l)*(s[i]-s[h[head]]-l);
        while(head<tail&&
        gt(h[tail],h[tail-1])*(s[i]-s[h[tail]])>=gt(i,h[tail])*(s[h[tail]]-s[h[tail-1]])) --tail;
        h[++tail]=i;
    }
    printf("%lld",f[n]);
    return 0;
}

题外话

当然,这道题也有1D/1D动态规划的做法,见链接,这里就不再赘述了。
这篇博客借鉴了laf的博客,感谢laf的帮助和支持。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值