LOJ2483. 「CEOI2017」Building Bridges (李超树+DP)

LOJ2483. 「CEOI2017」Building Bridges (李超树+DP)

题意

有n个建筑,每个建筑有两个权值(h[i],w[i]) ,h[i]表示建筑的高度,w[i]表示拆除建筑的费用.
现在要在除了头尾之外的n-2个建筑内选择若干个保留,并且保留头尾的建筑.
这样的费用为拆除所有没有保留建筑的费用+相邻的保留两个建筑高度差的平方.
求最小费用.

分析

首先有一个简单的dp思路
dp[i] 表示保留建筑i的最小费用.
s u m [ k ] = ∑ i = 1 k w [ i ] sum[k]=\sum_{i=1}^{k}w[i] sum[k]=i=1kw[i]

初始化:dp[1]=0
答案:dp[n]
转移方程:
d p [ i ] = m i n ( d p [ j ] + ( s u m [ i − 1 ] − s u m [ j ] ) + ( h [ i ] − h [ j ] ) 2 ) dp[i]=min(dp[j]+(sum[i-1]-sum[j])+(h[i]-h[j])^2) dp[i]=min(dp[j]+(sum[i1]sum[j])+(h[i]h[j])2)

然后就能获得40分的好成绩

然后来处理一下这个式子.
展开
d p [ j ] + s u m [ i − 1 ] − s u m [ j ] + h [ i ] ∗ h [ i ] + h [ j ] ∗ h [ j ] − 2 ∗ h [ i ] ∗ h [ j ] dp[j]+sum[i-1]-sum[j]+h[i]*h[i]+h[j]*h[j]-2*h[i]*h[j] dp[j]+sum[i1]sum[j]+h[i]h[i]+h[j]h[j]2h[i]h[j]
整理
d p [ j ] − s u m [ j ] + h [ j ] ∗ h [ j ] − 2 ∗ h [ i ] ∗ h [ j ] + s u m [ i − 1 ] + h [ i ] ∗ h [ i ] dp[j]-sum[j]+h[j]*h[j] -2*h[i]*h[j] +sum[i-1]+h[i]*h[i] dp[j]sum[j]+h[j]h[j]2h[i]h[j]+sum[i1]+h[i]h[i]
然后就很斜率优化了.


k = h [ j ] b = d p [ j ] − s u m [ j ] + h [ j ] ∗ h [ j ] k=h[j]\\ b=dp[j]-sum[j]+h[j]*h[j] k=h[j]b=dp[j]sum[j]+h[j]h[j]

每次查询就是给出一个x
x = − 2 ∗ h [ i ] x=-2*h[i] x=2h[i]

一个解决方法是CDQ分治
[但是我写不动
一个解决方法是treap
[但是我不会

可以发现需要操作的只有插入线和询问点的最大值.
然后就直接李超树维护一下,区间范围为[-2e6,0]

之后就没有了.
因为是全局插入所以插入线复杂度是log的.[然而如果是固定区间更新的话,复杂度是 l o g 2 log^2 log2
而查询单点是 l o g log log的.
所以复杂度是 n log ⁡ n n\log n nlogn

实现比较容易而且跑得快.
[至于李超树怎么操作请去看别的博客 写的都挺好的qwq?

code

为什么没有高亮

#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
#define pb push_back
#define A first 
#define B second
#define pii pair<int,int>
#define lowbit(p) (p&(-(p)))
using namespace std;
void read(int &x){
	x=0; char c=getchar(); int p=1;
	for (;c<48;c=getchar())if (c=='-')p=-1;
	for (;c>47;c=getchar())x=(x<<1)+(x<<3)+(c^48);
	x*=p;
}
void read(ll &x){
	x=0; char c=getchar(); int p=1;
	for (;c<48;c=getchar())if (c=='-')p=-1;
	for (;c>47;c=getchar())x=(x<<1)+(x<<3)+(c^48);
	x*=p;
}
void Min(int &x,int y){
	if (x>y)x=y;
}
void Min(ll &x,ll y){
	if (x>y)x=y;
}
void Max(int &x,int y){
	if (x<y)x=y;
}
void Max(ll &x,ll y){
	if (x<y)x=y;
}

#define M 100005
const ll inf=1e17;
ll h[M],dp[M],sum[M],w[M];
int n;
struct P1{
	void solve(){
		int i,j;
		for (i=2;i<=n;i++){
			dp[i]=inf;
			for (j=1;j<i;j++){
				Min(dp[i],dp[j]+(sum[i-1]-sum[j])+(h[j]-h[i])*(h[j]-h[i]));
			}
		}
		printf("%lld\n",dp[n]);
	}
}p1;
inline ll chk(ll k,ll b,ll x){
	return k*x+b;
}
#define N 2000005
int L,R,rt;
struct Reimu{
	ll kk[N],bb[N];
	int tot,ls[N],rs[N];
	void upd(int l,int r,ll k,ll b,int &p){
		if (!p){
			p=++tot;
			kk[p]=k;
			bb[p]=b;
			return;
		}
		if (chk(k,b,l)<=chk(kk[p],bb[p],l)&&chk(k,b,r)<=chk(kk[p],bb[p],r)){
			kk[p]=k;
			bb[p]=b;
			return ;
		}
		if (chk(k,b,l)>chk(kk[p],bb[p],l)&&chk(k,b,r)>chk(kk[p],bb[p],r)){
			return ;
		}
		if (l==r){
			return ;
		}
		int mid=(l+r)>>1;
		ll lk=kk[p],lb=bb[p];
		if (chk(k,b,l)<=chk(lk,lb,l)){
			if (chk(k,b,mid)<=chk(lk,lb,mid)){
				kk[p]=k;
				bb[p]=b;
				upd(mid+1,r,lk,lb,rs[p]);
				return ;
			}
			upd(l,mid,k,b,ls[p]);
			return ;
		}
		else{
			if (chk(k,b,mid+1)<=chk(lk,lb,mid+1)){
				kk[p]=k;
				bb[p]=b;
				upd(l,mid,lk,lb,ls[p]);
			}
			upd(mid+1,r,k,b,rs[p]);
		}
	}
	ll qu(int l,int r,int x,int p){
		if (!p)return inf;
		int mid=(l+r)>>1;
		if (x<=mid)return min(chk(kk[p],bb[p],x),qu(l,mid,x,ls[p]));
		return min(chk(kk[p],bb[p],x),qu(mid+1,r,x,rs[p]));
	}
}T;
struct P2{
	ll qu(ll x){
		return T.qu(L,R,x,rt);
	}
	void ins(ll k,ll b){
		T.upd(L,R,k,b,rt);
	}
	void solve(){
		int i;
		L=-2e6-5;
		R=5;
	/*
	
b=(dp[j]-sum[j]+h[j]*h[j])
k=h[j]

x=-2*h[i]
	*/
		ins(h[1],dp[1]-sum[1]+h[1]*h[1]);
		for (i=2;i<=n;i++){
			dp[i]=qu(-2*h[i])+h[i]*h[i]+sum[i-1];
			ins(h[i],dp[i]-sum[i]+h[i]*h[i]);
		}
		printf("%lld\n",dp[n]);
	}
}p2;

int main(){
//	freopen("1.in","r",stdin);
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	read(n);
	int i;
	for (i=1;i<=n;i++){
		read(h[i]);
	}
	for (i=1;i<=n;i++){
		read(w[i]);
		sum[i]=sum[i-1]+w[i];
	}
	if (n<=1000){
		p1.solve();
		return 0;
	}
	p2.solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值