首先有一个比较好想的结论(谁说的。。。一点都不好想!)就是最优的情况一定是有两端高于中间的一段平地。
因为一段本来有高度差的一起增高等于没用,所以我们可以把最终高度相等的作为一段,无论这段有多少个。
而这样做的条件是两边比中间高。
这样我们得到一个dp式子,这个是个二次函数,可以直接求出t的最小值
就是理想高度,这样就求出了最小的来更新,而对于j的选取,就是在i前面第一个比它高的为止,因为维护一个单调递减的
序列,如果拿出的点比i小,那么因为这个点前面的比这个点大(单调递减),所以可以作为j和i作为一组,当这个点比i高时
就不合法了 ,用单调队列可以优化掉正常要枚举的三维。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n;
ll c;
ll val[1000006];
ll ss[2][1000006];
int stak[1000006],top;
ll dp[1000006];
ll gett(int x,int y,ll mx)
{
ll a=y-x-1;
ll b=-2*(ss[0][y-1]-ss[0][x])-(x!=0)*1ll*c-(y!=n+1)*1ll*c;
ll cc=ss[1][y-1]-ss[1][x]+(x!=0)*1ll*val[x]*c+(y!=n+1)*1ll*val[y]*c;
ll t=-1*(ll)floor((double)b/((double)a*2.0)+0.5);
t=max(t,mx);
if(x!=0)t=min(t,val[x]);
if(y!=n+1)t=min(t,val[y]);
return a*t*t+b*t+cc;
}
int main()
{
freopen("construct.in","r",stdin);
freopen("construct.out","w",stdout);
scanf("%d%I64d",&n,&c);
for(int i=1;i<=n;i++)
{
scanf("%I64d",&val[i]);
ss[0][i]=ss[0][i-1]+val[i];
ss[1][i]=ss[1][i-1]+val[i]*val[i];
}
val[0]=val[n+1]=0x3f3f3f3f;
stak[top++]=0;
for(int i=1;i<=n+1;i++)
{
//dp[i]=dp[i-1]+(i==1||i==n+1)?0:abs(val[i]-val[i-1])*c;有毒的三步运算符
if(i==1||i==n+1)
{
dp[i]=dp[i-1];
}else
{
dp[i]=dp[i-1]+abs(val[i]-val[i-1])*c;
}
while(top>0&&val[stak[top-1]]<=val[i])
{
if(top>1)
{
dp[i]=min(dp[i],dp[stak[top-2]]+gett(stak[top-2],i,val[stak[top-1]]));
}
top--;
}
stak[top++]=i;
}
printf("%I64d",dp[n+1]);
return 0;
}