P4655 [CEOI2017]Building Bridges
因为两桥不能相交,故易得\(dp\)方程:\(dp[i]=\min\{dp[j]+sumw[i-1]-sumw[j]+(h[i]-h[j])^2\}\)
看起来似乎可以斜率优化,可惜\(h[i]\)不单调。
这里介绍一种很巧妙的做法:李超线段树。
整理方程:\(dp[i]-h^2[i]-sumw[i-1]=(-2h[j])*h[i]+dp[j]-sumw[j]+h^2[j]\)
发现等式右侧的取值是斜率为\(-2h[j]\),截距为\(dp[j]-sumw[j]+h^2[j]\)的直线。
相当于我们维护一个直线集,\(dp[i]\)的最小取值即为\(h[i]\)处的最小取值加\(h^2[i]+sumw[i-1]\)。
用李超线段树维护,时间复杂度\(O(nlogn)\)。
代码如下,仅供参考:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e5+10;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
int n,h[maxn],sw[maxn],dp[maxn],lim,tr[maxn<<6];
struct node{int k,b;}p[maxn];
inline int calc(int id,int pos){
if(id==0)return 1e16;
return p[id].k*pos+p[id].b;
}
inline void update(int h,int l,int r,int x){
if(!tr[h])return tr[h]=x,void();
int mid=(l+r)>>1;
int s=calc(tr[h],mid),t=calc(x,mid);
if(l==r){
if(s>t)tr[h]=x;
return;
}
if(p[tr[h]].k<p[x].k){
if(s<t)update(h<<1,l,mid,x);
else update(h<<1|1,mid+1,r,tr[h]),tr[h]=x;
}else{
if(s<t)update(h<<1|1,mid+1,r,x);
else update(h<<1,l,mid,tr[h]),tr[h]=x;
}
}
inline int query(int h,int l,int r,int x){
int mid=(l+r)>>1,val=calc(tr[h],x);
if(l==r)return val;
if(mid>=x)return min(val,query(h<<1,l,mid,x));
else return min(val,query(h<<1|1,mid+1,r,x));
}
signed main(){
n=read();
for(int i=1;i<=n;i++){
h[i]=read();
lim=max(lim,h[i]);
}
for(int i=1;i<=n;i++)
sw[i]=read()+sw[i-1];
dp[1]=0;p[1]=(node){-2*h[1],dp[1]-sw[1]+h[1]*h[1]};
update(1,0,lim,1);
for(int i=2;i<=n;i++){
dp[i]=query(1,0,lim,h[i])+sw[i-1]+h[i]*h[i];
p[i]=(node){-2*h[i],dp[i]-sw[i]+h[i]*h[i]};
update(1,0,lim,i);
}
printf("%lld\n",dp[n]);
return 0;
}
深深地感到自己的弱小。