\(\text{Solution:}\)
首先有一个基础\(dp:\)设\(dp_i\)表示前\(i\)个物品装完的最小价值,\(sum[i]\)是\(C\)的前缀和则:
\(dp_i=\min_{j<i}dp[j]+(i-j-1+sum[i]-sum[j]-L)^2\)
这东西是\(n^2\)的,\(50000\)的数据绝对炸。
考虑优化:去掉\(min\),令\(L=L+1,c[i]=c[i]+1\)得到:\(dp_i=dp_j+(sum[i]-sum[j]-L)^2\)
再继续拆:\(dp[i]=dp[j]+sum[i]^2+(sum[j]+L)^2-2sum[i]sum[j]-2Lsum[i]\)
分离\(i,j,ij\)有关的项:\(dp[j]+(sum[j]+L)^2=2sum[i]*sum[j]+dp[i]-sum[i]^2+2*L*sum[i]\)
那么这个方程可以看做为一个一次函数:\(y=kx+b\)的形式,其中\(y=dp[j]+(sum[j]+L)^2,k=2sum[i],x=sum[j],b=dp[i]-sum[i]^2+2Lsum[i]\)
那么我们要最小化\(dp[i]\)就要最小化截距\(b\).
把它看做一条直线,那么它与\(y\)轴的交点就是截距。
那么我们可以观察一下:对于三个点\(A,B,C\),当且仅当\(B\)是连接\(AB,BC\)的线段中向下凸的那一个点,它才可以被\(i\)所对的斜率为\(2sum[i]\)的直线首先截到。
于是我们可以维护一个向下的下凸包,最优决策必然在这个凸包上面。找最优决策我们可以二分。
继续考虑,能不能\(O(n)?\)观察到,我们的斜率是递增的(\(2sum[i]\)随着\(i\)的增大而增大),所以我们要找的最优决策点所对的线段在凸包上面显然是越来越向右的。
也就是说,如果上一个最优决策是\(j\),则下一个最优决策\(j'\)必然在\(j\)的右边。
这是决策点的单调性。
于是,我们可以只维护大于当前点斜率的直线,找到的第一条大于当前点斜率的直线所对应的决策点,就是我们要更新的最优决策点。
所以,单调队列可以上了。两点间斜率公式是\(\frac{Y_b-Y_a}{X_b-X_a}\),对应到本题当中即可。
更新队列的时候,如果当前队头的斜率小于当前\(i\)对应直线的斜率,则舍弃;找到的第一个就是我们要找的点。
加入\(i\)的时候,判断队尾是不是能够被删掉,一种常用的写法是\(slope(q[tail-1],q[tail])>=slope(q[tail],i)\)的时候,出队。因为它前面的点的斜率是要小于这个点与上一个点所连直线的斜率的。
以上,这题时间复杂度\(O(n).\)
再多说一句:至于为何要用直线斜率为\(2sum[i]\)的直线去扫,是因为当斜率确定,则直线的形状确定,唯一不知道的是截距。我们要最小化截距,还要由一个决策点转移而来,故我们可以从下往上平移这个直线,触碰到的第一个点必然是凸包上的下凸点,碰到第一个点必然最靠下,同理其截距也就最小。因为知道斜率\(k\),我们只需要再知道一个点,就可以唯一确定这一条直线了。于是,我们要找的点就是我们单调队列中所维护的第一个斜率大于当前斜率的点。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,L,dp[500010];
int sum[500010];
int q[500010],tail,head;
int sq(int x){return x*x;}
inline int Y(int x){return dp[x]+sq(sum[x]+L);}
inline int X(int x){return sum[x];}
double slope(int x,int y){
return (Y(y)-Y(x))/(X(y)-X(x));
}
signed main(){
scanf("%lld%lld",&n,&L);
for(int i=1;i<=n;sum[i]+=sum[i-1]+1,++i)scanf("%lld",&sum[i]);
L++;
/*for(int i=1;i<=n;++i){
for(int j=0;j<i;++j){
dp[i]=min(dp[i],dp[j]+sq(sum[i]-sum[j]-L));
}
}
printf("%lld\n",dp[n]);*/
//dp[i]=dp[j]+(i-j+sum[i]-sum[j]-L)^2
//dp[i]=dp[j]+(i^2-ij+isum[i]-isum[j]-iL)+(-ij+j^2-jsum[i]+jsum[j]+jL)+(isum[i]-jsum[i]+sum[i]^2-sum[i]*sum[j]-Lsum[i])+(-iL+jL-Lsum[i]+Lsum[j]+L^2)
//dp[i]=dp[j]+2(i-j-L+sum[i])sum[i]+(j-i+L)sum[j]+(i-j-L-L)i+(j+L+L)j-sum[i]*sum[j]+dp[j]
//-dp[j]- (j+L)sum[j]-(j+L+L)j = -sum[i]*sum[j]-jsum[i]-isum[j]-ij+ 2(i- L+sum[i])sum[i] +(i -L-L)i-dp[i]
//y = k x + b
//令c[i]++得
//dp[i]=dp[j]+(sum[i]-sum[j]-L)^2
//dp[i]=dp[j]+sum[i]^2+(sum[j]+L)^2-2sum[i]sum[j]-2sum[i]L
//dp[i]=dp[j]+(sum[j]+L)^2-2sum[i]sum[j]-2sum[i]L+sum[i]^2
//dp[j]+(sum[j]+L)^2=2sum[i]sum[j]+(2sum[i]L-sum[i]^2+dp[i])
//y =k x +b
//最小化b
//则维护一个下凸包
//斜率为2sum[i]
//那两点间的斜率是(Y(A)-Y(B))/(X(A)-X(B))
//此时A在右边
//维护下凸包方法一样,使得截距最小
//同样因为其决策点单调,维护所有斜率大于2sum[i]的直线,以此保证队头最优
//时间复杂度O(n)
//对应决策点坐标是(sum[x],dp[x]+sq(sum[x]+L))
//明显sum[x]单调递增
q[head]=0;
for(int i=1;i<=n;++i){
while(head<tail&&slope(q[head],q[head+1])<=2*sum[i])head++;
dp[i]=dp[q[head]]+sq(sum[i]-sum[q[head]]-L);
while(head<tail&&slope(q[tail-1],q[tail])>=slope(q[tail-1],i))tail--;
q[++tail]=i;
}
cout<<dp[n]<<endl;
return 0;
}