题解 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);
}
posted @ 2023-10-07 15:29  zhicheng123  阅读(24)  评论(0)    收藏  举报