题目链接
/*
假设sum[i]表示前i项和
dp[i]表示前i项需要花费的最少值
则dp[i]=min((sum[i]-sum[j])^2+m+dp[j]);//j=0~i-1
=>dp[i]=min(sum[i]*sum[i]+2*sum[i]*sum[j]+sum[j]*sum[j]+m+dp[j]);
由于存在sum[i]*sum[j]和i有关的这一项,所以不可能用单调队列来维护
2*sum[i]*sum[j]+sum[j]*sum[j]+m+dp[j]的最小值
这个时候怎么办??根据题意我们是要寻找到最优的j
所以假设k<j<i
有2*sum[i]*sum[j]+sum[j]*sum[j]+m+dp[j]<2*sum[i]*sum[k]+sum[k]*sum[k]+m+dp[k]
=>(sum[j]*sum[j]+dp[j]-(sum[k]*sum[k]+dp[k]))/(2*sum[j]-2*sum[k])<sum[i];
令
yj=sum[j]*sum[j]+dp[j]
yk=sum[k]*sum[k]+dp[k]
xj=2*sum[j];
xk=2*sum[k]
所以=>(yj-yk)/(xj-xk)<sum[i]则表示j比k更优
是连接点j和点k的直线的斜率
相对于点i最优的是j,
则在0~j相对于i+1,i+2...n点j肯定是最优的,因为满足sum[i+x]>sum[i]>yj-yk)/(xj-xk)
则只需要继续判断j+1~i+x之间是否有点更优,有的话j就不用保存了,这个过程用单调队列实行
而对于点i应该添加到队列中
比较i与点q[tail-1],q[tail-1]与点q[tail-2]的斜率k1,k2,
若k1<=k2则将点q[tail-1]去除,因为后面肯定是先k1<sum[i+x]
也就是点i肯定比q[tail-1]更优
然后重复比较直到k1>k2
*/
#include<cstdio>
const int N=5e5+10;
int n,m;
int dp[N],sum[N],q[N];
//dp[i]表示前i项需要花费的最少值
//sum[i]维护前缀和
//q维护单调队列
int GetY(int i,int j)//获取纵坐标
{
return sum[i]*sum[i]+dp[i]-(sum[j]*sum[j]+dp[j]);
}
int GetX(int i,int j)//获取横坐标
{
return 2*(sum[i]-sum[j]);
}
int main()
{
//freopen("in.txt","r",stdin);
int x;
while(~scanf("%d%d",&n,&m))
{
int head=0,tail=0;//头指针尾指针
q[tail++]=0;//这一步必须,因为可能前i个数全部作为一段才是最小值
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
sum[i]=sum[i-1]+x;//计算前缀和
while(head+1<tail&&GetY(q[head+1],q[head])<=GetX(q[head+1],q[head])*sum[i])++head;//更新最优的点
dp[i]=(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]])+m+dp[q[head]];//计算dp[i]的最小值
while(head+1<tail&&GetY(i,q[tail-1])*GetX(q[tail-1],q[tail-2])<=GetY(q[tail-1],q[tail-2])*GetX(i,q[tail-1]))--tail;
q[tail++]=i;
}
printf("%d\n",dp[n]);
}
return 0;
}