斜率优化略解

斜率优化略解

斜率优化在dp中是比较常见的优化方法

斜率优化算法最重要的一步,就是要将状态转移方程改写为y=kx+b的形式。而这一算法主要的难点就在于,对于一个复杂的状态转移方程,我们将哪些项将作为y,哪些项最为k和x,哪些项作为b。

我们以Luogu P3195 [HNOI2008]玩具装箱TOY为例来说斜率优化

在本题中,设前缀和为sum[i],由题意易得dp方程:

[dp[i]=Min(dp[j]+(sum[i]+i-sum[j]-j-L-1)^2) (j<i) ]

(O(N^2))的,过不了此题

(b[i]=sum[i]+i+L+1)(这一步是为了简化计算)

则(先忽略Min):

$$dp[i]=dp[j]+(a[i]-b[j])^2$$

展开得:

$$dp[i]=dp[j]+a[i]^2-2⋅a[i]⋅b[j]+b[j]^2$$

移项得(移的项实际比较套路):

$$2⋅a[i]⋅b[j]+dp[i]−a[i]^2=dp[j]+b[j]^2$$

我们把这个方程转化成y=kx+b的形式:

(dp[j]+b[j]^2)是y,(2⋅a[i])是k(斜率),(b[j])是x,(dp[i]-a[i]^2)是b(截距)

这个式子珂以变成平面直角坐标系上的一条一次函数

因为对于每个i来说,a[i]都是确定的

(a[i]^2)(一个定值)

而题目即为找这个截距的最小值

本题中可能为最优的P点组成了一个下凸包(其他题目可能不同,结合题目具体分析)

显然,凸包中相邻两点斜率是单调递增的

(2⋅a[i])也是单调递增的

(slope(P_j,P_{j+1}) > 2 cdot a[i])的点(slope(a,b)表示a,b两点连线的斜率)

(slope(P_j,P_{j+1}))

完整代码:

#include <bits/stdc++.h>
#define N 50005
#define db double
#define getchar nc 
using namespace std;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf; 
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; 
}
inline int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*f;
}
inline void write(register long long x)
{
    if(!x)putchar('0');if(x<0)x=-x,putchar('-');
    static int sta[35];register int tot=0;
    while(x)sta[tot++]=x%10,x/=10;
    while(tot)putchar(sta[--tot]+48);
}
int n,L;
db sum[N],dp[N];
int head,tail,q[N];
inline db a(register int i)
{
    return sum[i]+i;
}
inline db b(register int i)
{
    return a(i)+L+1;
}
inline db Y(register int i)
{
    return dp[i]+b(i)*b(i);
}
inline db slope(register int i,register int j)
{
    return (Y(i)-Y(j))/(b(i)-b(j));
}
int main()
{
    n=read(),L=read();
    head=tail=1;
    for(register int i=1;i<=n;++i)
    {
        sum[i]=(db)read();
        sum[i]+=sum[i-1];
        while(head<tail&&slope(q[head],q[head+1])<a(i)*2)
            ++head;
        dp[i]=dp[q[head]]+(a(i)-b(q[head]))*(a(i)-b(q[head]));
        while(head<tail&&slope(i,q[tail-1])<slope(q[tail-1],q[tail]))
            --tail;
        q[++tail]=i; 
    }
    write((long long)dp[n]);
    return 0;
}

在这道题中,相邻决策点斜率和每个点的横坐标递增,但在其他题目中还会出现以下情况

一、相邻两个决策点的斜率不递增

这种情况下,我们无法保证小于等于当前斜率的决策点j一定也小于下一个状态的斜率。因此不能进行“弹掉队首元素”这一步。但是这不影响我们维护一个下凸壳,因此我们可以在单调队列(下凸壳)内进行二分查找,找到第一个使该决策点和下一个决策点构成的线段斜率>k的决策点,即为当前最优决策。

二、每个决策点横坐标不递增

这意味着要在凸壳任意位置动态插入顶点、动态查询,我们珂以使用平衡树来维护凸壳。