BZOJ1010 [HNOI2008]玩具装箱toy
Description
划分一个数列,区间[l,r]的值
x=r−l+∑k=lrCk
求
∑(xi−L)2
最小值。
题解
可以很容易想出状态转移方程 ,记sum[i]为前缀和,
f[i]=max(f[j]+(i−j+1+sum[i]−sum[j]−l)2)
但复杂度为
O(n2)
,铁定TLE。
我们发现(i-j+1+sum[i]-sum[j]-l)^2由i与j共同决定,可以用斜率优化。
假设
k<j<i
,如果j比k有,则有
f[i]−f[k]+(j+sum[j])2+(k+sum[k])2j−sum[j]−k−sum[k]<2(i+s[i]−L−1)
左边只与决策有关,右边只与状态有关,就可以用斜率优化。
#include <stdio.h>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAXN 50000+10
#define LL long long
using namespace std;
LL c[MAXN],l,sum[MAXN],f[MAXN];
int n,q[MAXN],h,t;
LL up(int i,int j) {return f[i]-f[j]+(i+sum[i])*(i+sum[i])-(j+sum[j])*(j+sum[j]);}
LL down(int i,int j) {return i+sum[i]-j-sum[j];}
int main()
{
scanf("%d%lld",&n,&l);
for(int i=1;i<=n;i++)
scanf("%lld",&c[i]),sum[i]+=sum[i-1]+c[i],f[i]=~(1<<31);
f[0]=0;q[1]=0;h=1;t=2;
for(int i=1;i<=n;i++)
{
while(h<t-1&&up(q[h+1],q[h])/(1.0*down(q[h+1],q[h]))<2*(i+sum[i]-l-1)) h++;
f[i]=f[q[h]]+(i-q[h]-1+sum[i]-sum[q[h]]-l)*(i-q[h]-1+sum[i]-sum[q[h]]-l);
while(h<t-1&&up(q[t-1],q[t-2])/(1.0*down(q[t-1],q[t-2]))>up(i,q[t-2])/(1.0*down(i,q[t-2])))t--;
q[t++]=i;
}
printf("%lld\n",f[n]);
return 0;
}