对于一些具有决策单调性的dp题目,我们可以应用斜率优化将复杂度从O(n^2)降到O(n)。

bzoj1010 HNOI2008 玩具装箱toy

题目大意:对于一些一维长度的物品,我们可以将连续的i~j个物品放在一起,费用是(j-i+sigma lk(i<=k<=j)-L)^2,求n个物品最小费用。

思路:对于这类dp题目,我们可以通过进行一系列数学化简,找出其中的斜率,进而斜率优化。我们设sum[i]表示前i件物品的长度和,s[i]为sum[i]+i,dp[i]=dp[j]+(s[i]-s[j]-L)^2。我们取j<k,dp[j,i]-dp[k,i]>0,带入之前的式子,将L+1,将含i的移到右边,含j、k的移到另一边,就会得到((dp[k,i]+sk^2)-(dp[j,i]+sj^2))/(sk-sj)<2(si-L)①,我们设左边=w[k,j],显然当w[k,j]<右边的时候,k更优。对于j<k<i,w[i,k]<w[k,j]②时,k不可能是之后用到的最优解,所以可以舍弃。我们维护可能用到的最优解,不断的舍弃那些不好的解,就能将复杂度降至O(n)。对于这里涉及两个符号的问题,我们可以统一符号(如上两个式子)。

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxnode 50005
using namespace std;
long long dp[maxnode]={0},sum[maxnode]={0},q[maxnode]={0},l;
long long getup(int j,int k)
{
    return dp[j]+(sum[j])*(sum[j])-(dp[k]+(sum[k])*(sum[k]));
}
long long getdown(int j,int k)
{
    return (sum[j]-sum[k]);
}
long long getdp(int i,int j)
{
    return dp[j]+(sum[i]-sum[j]-l)*(sum[i]-sum[j]-l);
}
double g(int j,int k)
{
    return getup(j,k)*1.0/(2.0*getdown(j,k));
}
int main()
{
    int n,i,j,head,tail;
    scanf("%d%lld",&n,&l);
    for (i=1;i<=n;++i) 
    {
      scanf("%lld",&sum[i]);
      sum[i]+=sum[i-1];
    }
    for (i=1;i<=n;++i) sum[i]+=(long long)i;
    head=tail=1;q[tail]=0;++l;
    for (i=1;i<=n;++i)
    {
        while(head<tail&& g(q[head+1],q[head]) < 1.0*(sum[i]-l))
          ++head;
        dp[i]=getdp(i,q[head]);
        while(head<tail&& g(i,q[tail]) < g(q[tail],q[tail-1]))
          --tail;
        q[++tail]=i;
    }
    printf("%lld\n",dp[n]);
}
View Code

相关文章: