设$f[i]$表示装$i$个玩具用的最小花费,
原转移方程:$f[i]=min(f[j]+(sum[i]-sum[j]+i-(j+1)+L)^2$
把与$i$和$j$相关的放到一起:$f[i]=min(f[j]+(sum[i]+i-(sum[j]+j+L+1))^2$
不断化简:设$a[i]=sum[i]+i,b[i]=sum[i]+i+L+1$,
则:$f[i]=min(f[j]+(a[i]-b[j])^2)$
对于$k,j$,若$j$比$k$优,则有:
$f[j]+(a[i])^2+(b[j])^2-2*(a[i])*(b[j])<=f[k]+(a[i])^2+(b[k])^2-2*(a[i])*(b[k])$
化简:$f[j]-f[k]+(b[j])^2-(b[k])^2<=2*(b[j]-b[k])*(a[i])$
写成斜率形式:$frac{f[j]-f[k]+(b[j])^2-(b[k])^2}{b[j]-b[k]}<=2*a[i]$
$a[i],b[i]$都是递增的,可以直接斜率优化
#include<iostream> #include<cstring> #include<cstdio> #define ll long long using namespace std; const int maxn=50009; int n; ll l,f[maxn],s[maxn]; int q[maxn],head,tail; inline ll checka(int j,int k){ return f[j]-f[k]+(s[j]+j+l+1+s[k]+k+l+1)*(s[j]+j-s[k]-k); } inline ll checkb(int j,int k){ return s[j]+j-s[k]-k; } int main(){ scanf("%d%lld",&n,&l); for(int i=1;i<=n;i++) scanf("%lld",&s[i]); for(int i=1;i<=n;i++)s[i]+=s[i-1]; head=tail=1; for(int i=1;i<=n;i++){ while(head<tail && checka(q[head+1],q[head])<=2*(s[i]+i)*checkb(q[head+1],q[head]))head++; f[i]=f[q[head]]+(s[i]+i-s[q[head]]-q[head]-l-1)*(s[i]+i-s[q[head]]-q[head]-l-1); while(head<tail && checka(i,q[tail])*checkb(q[tail],q[tail-1])<=checka(q[tail],q[tail-1])*checkb(i,q[tail]))tail--; q[++tail]=i; } printf("%lld",f[n]); }