非常有趣的dp...
记dp[i]表示更新到了i且i不变的最小代价
那么可以推知,dp[i]可以由之前的dp[j]来更新,如果这样来更新,一定有i~j之间的所有楼房被修改成了相同的高度且这个高度小于i和j的高度
那么我们可以进行转移:
其中t为j~i之间楼房被修改成的高度
发现要枚举的t其实是一个二次函数的形式,于是对他进行化简得:
二次函数求最值需要求对称轴,当
时取得最小值,所以只需求出此时上述表达式的值即可
同时发现如果要求中间的高度<hi,那么如果出现了hj>hi,这个j之前的值都不能更新dp[i],所以用单调结构维护即可,可以把复杂度降到O(n)
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3fll
using namespace std;
ll dp[1000005];
ll my_stack[1000005];
ll s1[1000005];
ll s2[1000005];
ll h[1000005];
ll n,c;
int ttop=0;
inline ll read()
{
ll f=1,x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
ll solve(ll l,ll r,ll maxh)
{
ll a=r-l-1;//二次项系数
ll b=-2*(s1[r-1]-s1[l]);
if(l!=0)
{
b-=c;
}
if(r!=n+1)
{
b-=c;
}
ll C=s2[r-1]-s2[l];
if(l!=0)
{
C+=c*h[l];
}
if(r!=n+1)
{
C+=c*h[r];
}
ll t=(ll)floor((double)b/(double)(-2.0*(double)a)+0.5);
t=max(t,maxh);
t=min(t,min(h[l],h[r]));
return a*t*t+b*t+C;
}
int main()
{
freopen("construct.in","r",stdin);
freopen("construct.out","w",stdout);
n=read(),c=read();
for(int i=1;i<=n;i++)
{
h[i]=read();
s1[i]=s1[i-1]+h[i];
s2[i]=s2[i-1]+h[i]*h[i];
}
h[0]=h[n+1]=INF;
my_stack[++ttop]=0;
for(int i=1;i<=n+1;i++)
{
if(i==1||i==n+1)
{
dp[i]=dp[i-1];
}else
{
dp[i]=dp[i-1]+c*abs(h[i]-h[i-1]);
}
while(ttop>1&&h[my_stack[ttop]]<=h[i])
{
if(ttop>=2)
{
dp[i]=min(dp[i],dp[my_stack[ttop-1]]+solve(my_stack[ttop-1],i,h[my_stack[ttop]]));
}
ttop--;
}
my_stack[++ttop]=i;
}
printf("%I64d\n",dp[n+1]);
return 0;
}