[HNOI2008]玩具装箱TOY

显然dp方程可以得出为dp[i]=min{dp[j]+(sum[i]+isum[j]jL1)2}

但是这种dp方式是O(n2)的,我们可以使用斜率优化的方法将其优化成O(n)

我们考虑将原DP方程变形

1.  dp[i]=dp[j]+(sum[i]+isum[j]jL1)2      将L++

2.  dp[i]=dp[j]+(sum[i]+isum[j]jL)2          令sum[i]+=i

3.  dp[i]=dp[j]+(sum[i]sum[j]L)2

将原式子展开

dp[j]+sum[i]2+(sum[j]+L)2=2*sum[i]*(sum[j]+L)+dp[i]

      y                 =     k      *        x       +  b

y=dp[j]+sum[i]2+(sum[j]+L)2     k=2*sum[i]   x=sum[j]+L   b=dp[i]

所以我们将每一个点(x,y)画在图像上,并维护一个凹包

那么我们在求f[i]的值的时候,用一条斜率为2*sum[i]的线切上它能切到的最低点

此时其与y轴的交点的截距为f[i]的最小值

又因为sum[i]单调递增,所以k值只会越来越大

所以选中点前的点全部废弃

 因为每个点只会被删一次,所以算法为O(n)

实现采用单调队列:

#include <algorithm>
#include <iostream>
#include <cmath>
#include <cstring>
#include <map>
#include <string>
#include <vector>
#include <queue>
#include <stack>
#include <cstdio>
#include <cstdlib>
using namespace std;
typedef long long ll;
inline ll read()
{
    register ll p(1),a(0);register char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if(ch=='-') p=-1,ch=getchar();
    while(ch>='0'&&ch<='9') a=a*10+ch-48,ch=getchar();
    return a*p;
}
const int N=50010;
ll n,L,f[N],s[N],Q[N],head=1,tail=1;
//f[i]+2*s[i]*(s[j]+L)=f[j]+s[i]^2+(s[j]+L)^2
double getx(int x) {return s[x];}
double gety(int x) {return f[x]+(s[x]+L)*(s[x]+L);}
double che(int x,int y){return (gety(y)-gety(x))/(getx(y)-getx(x));}
int main()
{
//    freopen("input","r",stdin);
//    freopen("output","w",stdout);
    n=read(),L=read()+1;
    for(int i=1;i<=n;i++) s[i]=s[i-1]+read();
    for(int i=1;i<=n;i++) s[i]+=i;
    for(int i=1,j;i<=n;i++)
    {
        while(head<tail&&che(Q[head],Q[head+1])<2*s[i]) ++head;
        j=Q[head];f[i]=f[j]+(s[i]-s[j]-L)*(s[i]-s[j]-L);
        while(head<tail&&che(Q[tail-1],Q[tail])>che(Q[tail],i)) --tail;
        Q[++tail]=i;
    }
    printf("%lld",f[n]);
    return 0;
}
/*

*/
原文地址:https://www.cnblogs.com/cold-cold/p/10103100.html