题解 AT_dp_z Frog 3
分析
首先可以列出最基础的 DP 式子。设 \(dp_i\) 表示跳到 \(i\) 的最小花费,有:
\[dp_i=\min\limits_{1\leq j < i
}\{dp_j+(h_i-h_j)^2\}+C\]
\[dp_1=0
\]
直接算的话时间复杂度 \(O(n^2)\)。
然后化简一下式子,有:
\[dp_i=\min\limits_{1\leq j<i}\{dp_j+h_j^2-2h_ih_j\}+h_i^2+C
\]
然后就可以使用李超线段树维护了。具体地,计算出一个 \(dp_j\) 之后,我们并不知道后面的哪个 \(h_i\) 会从它转移过去,因此我们对 \(h\) 维护一棵李超线段树,把 \(h_i\) 看作自变量,插入一条斜率为 \(-2h_j\),截距为 \(dp_j+h_j^2\) 的直线。每次要求 \(dp_i\) 时询问 \(h_i\) 处值最小的一条直线的值再加上 \(h_i^2+C\) 即可。时间复杂度 \(O(n\log n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=200010,M=1000010;
int h[N],p[M<<2];
struct line{
ll k,b;
}pp[N];
ll calc(int now,int x){
return pp[now].k*x+pp[now].b;
}
bool cmp(int a,int b,int x){
return calc(a,x)<calc(b,x);
}
void update(int now,int l,int r,int rt){
int mid=(l+r)>>1;
if(cmp(now,p[rt],l)&&cmp(now,p[rt],r)){
p[rt]=now;
return;
}
else if(!cmp(now,p[rt],l)&&!cmp(now,p[rt],r)){
return;
}
if(cmp(now,p[rt],mid)){
swap(now,p[rt]);
}
if(cmp(now,p[rt],l)){
update(now,l,mid,rt<<1);
}
if(cmp(now,p[rt],r)){
update(now,mid+1,r,rt<<1|1);
}
}
int query(int x,int l,int r,int rt){
int mid=(l+r)>>1,res;
if(l==r){
return p[rt];
}
if(x<=mid){
res=query(x,l,mid,rt<<1);
}
else{
res=query(x,mid+1,r,rt<<1|1);
}
if(cmp(p[rt],res,x)){
res=p[rt];
}
return res;
}
int main(){
int n,summ=0,maxx=0;
ll ans,c;
scanf("%d%lld",&n,&c);
for(int i=1;i<=n;i++){
scanf("%d",&h[i]);
maxx=max(maxx,h[i]);
}
pp[0].b=1e18;
pp[++summ].k=-2*h[1];
pp[summ].b=1ll*h[1]*h[1];
update(summ,1,maxx,1);
for(int i=2;i<=n;i++){
ans=calc(query(h[i],1,maxx,1),h[i])+1ll*h[i]*h[i]+c;
pp[++summ].k=-2*h[i];
pp[summ].b=ans+1ll*h[i]*h[i];
update(summ,1,maxx,1);
}
printf("%lld",ans);
}