楼房重建 与 线段树前缀最大值

楼房重建 与 线段树前缀最大值

P4198 楼房重建

先来看原题:P4198 楼房重建

\(s_i=\frac{H_i}{i}\),即斜率。

本质是要求 \(\max_{i=1}^{j-1}s_i\le s_j\) 的个数。

由于带单点修改,我们考虑在线段树上维护这个信息。

对线段树上每个节点 \(x\) 代表的区间 \([l,r]\) 维护两个值:

区间最大的 \(s_i\)\(mx_x\),只考虑当前区间内时上述式子的答案 \(f_x\)

最后的答案即 \(f_{root}\)

前者是基本操作。考虑后者。

后者不能简单地把两个子区间的答案加起来,因为没有考虑左区间对右区间的贡献。

于是我们用一个递归函数 \(calc(x,l,r,pre)\) 来计算右区间的答案。

其表示线段树上编号为 \(x\) 的点,其代表区间为 \([l,r]\),区间内大于 \(pre\) 的答案。

\(f_x=f_{ls}+calc(rs,mid+1,r,mx_{ls})\)

\[calc(x,l,r,pre)= \begin{equation} \begin{cases} f_x & l=r\and pre<mx_x \\ 0 & l=r\and pre\ge mx_x \\ calc(ls,l,mid,pre)+f_x-f_{ls} & l\neq r\and pre<mx_{ls}\\ calc(rs,mid+1,r,pre) & l\neq r\and pre\ge mx_{ls} \end{cases} \end{equation} \]

解释:第一行和第二行显然,考虑第三行和第四行。

第三行当 \(pre\) 小于左区间时,\(pre\) 对右区间没有贡献,于是直接加上右区间的贡献即 \(f_x-f_{ls}\),然后递归处理左区间。

第四行当 \(pre\) 不小于左区间时,左区间答案为 0,于是直接递归右区间。

这样就能在 \(O(\log n)\)\(PushUp\) 一个节点,修改一个值就是 \(O(\log ^2n)\) 的。

#include<bits/stdc++.h>
using namespace std;
int n,m;
#define ld long double
const int N=1e5+5;
struct tree{
	#define ls (x<<1)
	#define rs (ls|1)
	#define mid ((l+r)>>1)
	ld mx[N*4]; int f[N*4];
	int calc(int x,int l,int r,ld pre){
		if(l==r){
			if(pre<mx[x])return f[x];
			return 0;
		}
		if(pre<mx[ls])return calc(ls,l,mid,pre)+f[x]-f[ls];
		return calc(rs,mid+1,r,pre);
	}
	void update(int x,int l,int r,int y,ld z){
		if(l==r){
			mx[x]=z,f[x]=1;
			return;
		}
		if(y<=mid)update(ls,l,mid,y,z);
		else update(rs,mid+1,r,y,z);
		mx[x]=max(mx[ls],mx[rs]);
		f[x]=f[ls]+calc(rs,mid+1,r,mx[ls]);
	}
}tr;
int main(){
//	freopen("build.in","r",stdin),freopen("build.out","w",stdout);
	ios::sync_with_stdio(0); cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		tr.update(1,1,n,x,(ld)y/x);
		cout<<tr.f[1]<<"\n";
	}
}

P4425 [HNOI/AHOI2018] 转盘

题面

考虑确定起点时,等到最后一个出现,然后走一圈不停下收掉。

把环拉成 \(2n\) 的链,答案就是:

\[\min_{i=1}^n\{\max_{j=i}^{i+n-1}\{T_j-(j-i)\}\}+n-1 \]

\(j-i\)\(j\) 离起点的距离,\(n-1\) 是走一圈的花费。

\(a_i=T_i-i\),则

\[\min_{i=1}^n\{\max_{j\ge i}\{a_j\}+i\}+n-1 \]

对于 \(j>i+n-1\) 的部分不会有影响,所以取 \(j\ge i\),这是一个后缀最大值的形式,用楼房重建的技巧。

时间复杂度 \(O(n\log ^2 n)\)

#include<bits/stdc++.h>
using namespace std;
int n,m,p;
const int N=2e5+5;
int t[N];
struct tree{
	#define ls (x<<1)
	#define rs (ls|1)
	#define mid ((l+r)>>1)
	int mx[N*4],f[N*4],f1[N*4];
	int calc(int x,int l,int r,int pre){
		if(l==r){
			return max(pre,mx[x])+l;
		}
		if(pre<mx[rs])return min(f1[x],calc(rs,mid+1,r,pre));
		else return min(pre+mid+1,calc(ls,l,mid,pre));
	}
	void build(int x,int l,int r){
		if(l==r){
			mx[x]=t[l],f[x]=t[l]+l;
			return;
		}
		build(ls,l,mid),build(rs,mid+1,r);
		mx[x]=max(mx[ls],mx[rs]);
		f[x]=min(f[rs],f1[x]=calc(ls,l,mid,mx[rs]));
	}
	void update(int x,int l,int r,int y,int z){
		if(l==r){
			mx[x]=z,f[x]=z+l;
			return;
		}
		if(y<=mid)update(ls,l,mid,y,z);
		else update(rs,mid+1,r,y,z);
		mx[x]=max(mx[ls],mx[rs]);
		f[x]=min(f[rs],f1[x]=calc(ls,l,mid,mx[rs]));
	}
	int query(int x,int l,int r,int L,int R,int mx1){
		if(r<L||R<l)return 1e9;
		if(L<=l&&r<=R){
			return calc(x,l,r,mx1);
		}
		return min(query(ls,l,mid,L,R,max(mx1,mx[rs])),query(rs,mid+1,r,L,R,mx1));
	}
}tr;
int lastans=0;
int main(){
//	freopen("circle.in","r",stdin);
//	freopen("circle.out","w",stdout);
	ios::sync_with_stdio(0); cin.tie(0);
	cin>>n>>m>>p;
	for(int i=1;i<=n;i++){
		cin>>t[i]; t[i]-=i;
		t[i+n]=t[i]-n;
	}
	tr.build(1,1,2*n);
	lastans=tr.query(1,1,2*n,1,n,-1e9)+n-1;
	cout<<lastans<<"\n";
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		if(p)x^=lastans,y^=lastans;
		tr.update(1,1,2*n,x,y-x);
		tr.update(1,1,2*n,x+n,y-x-n);
		lastans=tr.query(1,1,2*n,1,n,-1e9)+n-1;
		cout<<lastans<<"\n";
	}
}
posted @ 2024-09-26 21:33  dengchengyu  阅读(5)  评论(0编辑  收藏  举报