【洛谷 P4655】Building Bridges | dp、斜率优化dp、李超线段树

题目大意:

n n n 根柱子依次排列,每根柱子都有一个高度。第 i i i根柱子的高度为 h i h_i hi

现在想要建造若干座桥,如果一座桥架在第 ii 根柱子和第 jj 根柱子之间,那么需要 ( h i − h j ) 2 (h_i-h_j)^2 (hihj)2的代价。

在造桥前,所有用不到的柱子都会被拆除,因为他们会干扰造桥进程。第 i i i根柱子被拆除的代价为 w i w_i wi,注意 w i w_i wi 不一定非负,因为可能政府希望拆除某些柱子。

现在政府想要知道,通过桥梁把第 1 1 1根柱子和第 n n n 根柱子连接的最小代价。注意桥梁不能在端点以外的任何地方相交。

题目思路:

这题可以用 斜率优化 d p dp dp,处理凸包来做。

这里介绍李超线段树的写法:

这种考虑 d p dp dp处理,用 d p i dp_i dpi代表当前跳到 i i i位置时最少的花费,所以就有( f j f_j fj代表前 w w w数组的前 j j j项和):
d p i = m a x ( d p j + ( h i − h j ) 2 + f i − 1 − f j , d p i ) dp_i = max(dp_j+(h_i-h_j)^2 + f_{i-1}-f_j,dp_i) dpi=max(dpj+(hihj)2+fi1fj,dpi)

令上述转移方程拆项:

d p i = m a x ( d p j + h j 2 − 2 ∗ h i ∗ h j − f j + h i 2 + f i − 1 ) dp_i = max(dp_j + h_j^2-2*h_i*h_j-f_j + h_i^2 +f_{i-1}) dpi=max(dpj+hj22hihjfj+hi2+fi1)

所以这个值可以讲与 i i i有关的提出来,那么剩下的:

d p j + h j 2 − 2 ∗ h i ∗ h j − f j dp_j + h_j^2-2*h_i*h_j-f_j dpj+hj22hihjfj这个最大就好了

如何处理这个最大值呢?

可以将 d p j + h j 2 − 2 ∗ h i ∗ h j − f j dp_j + h_j^2-2*h_i*h_j-f_j dpj+hj22hihjfj看成一个一次函数表达式:

  • y = k ∗ x + b y = k*x + b y=kx+b
  • k = − 2 ∗ h j k = -2*h_j k=2hj
  • b = d p j + h j 2 − f j b = dp_j + h_j^2-f_j b=dpj+hj2fj
  • x = h i x = h_i x=hi

这样看来只需要用李超线段树维护之前的一次函数,求一下在 h i h_i hi处的最值就可以轻松解决啦!

Code:

/*** keep hungry and calm CoolGuang!  ***/
//#pragma GCC optimize(3)
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
#define dl(x) printf("%lld\n",x);
#define di(x) printf("%d\n",x);
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const ll INF= 1e18+7;
const ll maxn = 1e6+700;
const int M = 1e6+8;
const ll mod= 998244353;
const double eps = 1e-9;
const double PI = acos(-1);
template<typename T>inline void read(T &a){char c=getchar();T x=0,f=1;while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}a=f*x;}
ll n,m,p;
ll num[maxn];
ll h[maxn],w[maxn];
ll sum[maxn];
ll a[maxn*5],b[maxn*5];
int tag[maxn*5];
void Insert(int k,int l,int r,int x,int y,ll c,ll d){
	int mid = (l+r)/2;
	if(x<=l && y>=r){
		if(!tag[k]){
			tag[k] = 1,a[k] = c,b[k] = d;
			return ;
		}
		ll opx = a[k]*mid + b[k],opy = c*mid + d;
		if(l == r){if(opx<opy) {a[k] =c,b[k] = d;}return;}
		if(opx > opy){///旧的比新的小
			if(a[k]<c){
				Insert(k<<1|1,mid+1,r,x,y,a[k],b[k]);
				a[k] = c;b[k] = d;
			}else{
				Insert(k<<1,l,mid,x,y,a[k],b[k]);
				a[k] = c;b[k] = d;
			}
		}else{
			if(a[k]<c) Insert(k<<1,l,mid,x,y,c,d);
			else Insert(k<<1|1,mid+1,r,x,y,c,d);
		}
		return;
	}
	if(x<=mid) Insert(k<<1,l,mid,x,y,c,d);
	if(y>mid) Insert(k<<1|1,mid+1,r,x,y,c,d);
}
ll FindMin(int k,int l,int r,int pos){
	if(l == r) return tag[k]?a[k]*pos*1ll+b[k]:INF; 
	int mid = (l+r)/2;
	ll ans = tag[k]?a[k]*pos*1ll+b[k]:INF; 
	if(pos <= mid) return min(ans,FindMax(k<<1,l,mid,pos));
	return  min(ans,FindMax(k<<1|1,mid+1,r,pos));
}
int main(){
	read(n);
	for(int i=1;i<=n;i++) read(h[i]);
	for(int i=1;i<=n;i++){
		read(w[i]);
    	sum[i] = sum[i-1] + w[i];
    }
    ll ans = INF;
    Insert(1,0,M,0,M,-2*h[1],h[1]*h[1]-sum[1]);
    for(int i=2;i<=n;i++){
    	ll temp = h[i]*h[i] + sum[i-1] + FindMax(1,0,M,h[i]);
    	Insert(1,0,M,0,M,-2*h[i],temp+h[i]*h[i]-sum[i]);
    	ans = temp;
    }
    printf("%lld\n",ans);
    return 0;
}
/***
12152
1 1 0 0 0 0 0 0 0
1 2 3 4 5 6 7 8 9
***/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只酷酷光儿( CoolGuang)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值