【CEOI2017】Building Bridges【任意坐标斜率优化】【李超线段树】

题意:有 n n n 个柱子,每个柱子有高度 h i h_i hi。你需要在柱子间修桥,在 i , j i,j i,j 间修桥代价为 ( h i − h j ) 2 (h_i-h_j)^2 (hihj)2,桥梁只能在柱子处相交,未安装桥的柱子需要拆除,代价为 w i w_i wi(可能为负数)。求让 1 1 1 n n n 连接的最小代价。

n ≤ 1 0 5 , h i , ∣ w i ∣ ≤ 1 0 6 n\leq 10^5,h_i,|w_i|\leq 10^6 n105,hi,wi106

显然是斜率优化

f i f_i fi 表示从 1 1 1 修到 i i i 的最小代价, s s s w w w 前缀和。

f i = min ⁡ 1 ≤ j < i { f j + ( h i − h j ) 2 + s i − 1 − s j } f_i=\min_{1\leq j<i}\{f_j+(h_i-h_j)^2+s_{i-1}-s_j\} fi=1j<imin{fj+(hihj)2+si1sj}

f i = min ⁡ 1 ≤ j < i { f j + h i 2 − 2 h i h j + h j 2 + s i − 1 − s j } f_i=\min_{1\leq j<i}\{f_j+h^2_i-2h_ih_j+h^2_j+s_{i-1}-s_j\} fi=1j<imin{fj+hi22hihj+hj2+si1sj}

f i = h i 2 + s i − 1 + min ⁡ 1 ≤ j < i { − 2 h j ⋅ h i + f j + h j 2 − s j } f_i=h^2_i+s_{i-1}+\min_{1\leq j<i}\{-2h_j\cdot h_i+f_j+h_j^2-s_j\} fi=hi2+si1+1j<imin{2hjhi+fj+hj2sj}

每个决策 j j j 看成斜率为 − 2 h j -2h_j 2hj,截距为 f j + h j 2 − s j f_j+h_j^2-s_j fj+hj2sj 的直线, h i h_i hi 看成自变量,李超线段树求最小值即可。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
using namespace std;
const int N=1e6,MAXN=N+5;
typedef long long ll;
inline int read()
{
	int ans=0,f=1;
	char c=getchar();
	while (!isdigit(c)) (c=='-')&&(f=-1),c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return f*ans;
}
ll k[MAXN],b[MAXN];
inline ll calc(int i,int x){return k[i]*x+b[i];}
#define lc p<<1
#define rc p<<1|1
int mn[MAXN<<2];
void modify(int p,int l,int r,int v)
{
	int mid=(l+r)>>1;
	if (!mn[p]) return (void)(mn[p]=v);
	if (calc(mn[p],l)<=calc(v,l)&&calc(mn[p],r)<=calc(v,r)) return;
	if (calc(mn[p],l)>=calc(v,l)&&calc(mn[p],r)>=calc(v,r)) return (void)(mn[p]=v);
	if (calc(mn[p],mid)>calc(v,mid)) swap(mn[p],v);
	if (calc(mn[p],l)>calc(v,l)) modify(lc,l,mid,v);
	else modify(rc,mid+1,r,v);
}
void query(int p,int l,int r,int k,ll& ans)
{
	if (mn[p]) ans=min(ans,calc(mn[p],k));
	if (l==r) return;
	int mid=(l+r)>>1;
	if (k<=mid) query(lc,l,mid,k,ans);
	else query(rc,mid+1,r,k,ans);
}
ll h[MAXN],s[MAXN],f[MAXN];
int main()
{
	int n=read();
	for (int i=1;i<=n;i++) h[i]=read();
	for (int i=1;i<=n;i++) s[i]=s[i-1]+read();
	k[1]=-2*h[1],b[1]=h[1]*h[1]-s[1];
	modify(1,0,N,1);
	for (int i=2;i<=n;i++)
	{
		query(1,0,N,h[i],f[i]=1e18);
		f[i]+=h[i]*h[i]+s[i-1];
		k[i]=-2*h[i],b[i]=h[i]*h[i]+f[i]-s[i];
		modify(1,0,N,i);
	}
	cout<<f[n];
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值