单侧递归线段树

简介

线段树最重要的操作就是 pushup 和 pushdown,对于一类问题,光凭当前节点的信息无法合并,需要用到它的子节点的信息才能合并,这就是线段树单侧递归问题,时间复杂度一般是 \(O(nlog^2n)\) 。

通常解决区间前缀最值问题

例题

我们先将楼转化成斜率,那么这道题相当于求前缀最大值的个数,我们考虑如何上传

定义 \(find(p,v)\) 为前缀最大已为 \(v\),在 \(p\) 节点所代表区间的中前缀最大的个数,那么显然有 \(ans_p=ans_{ls}+find(rs,max_{ls})\)(\(max\) 代表区间最值)

我们考虑分类讨论求 \(find\)

  1. \(l=r\),只有一个节点,直接判断即可
  2. \(max_{p}\le v\),整个区间不可能有最大值,返回0
  3. \(max_{ls}\le v\),左边不可能,递归右边(如图)image.png
  4. \(max_{ls}>v\),此时右侧的前缀最大值一定都大于v,那么可以直接用 \(ans_{p}-ans_{ls}\) 求出右侧答案,然后只递归左侧(如图)image.png
#include<bits/stdc++.h>
using namespace std;
#define db double
#define mid ((l+r)>>1)
#define ls(p) (p<<1)
#define rs(p) ((p<<1)|1)
#define lson ls(p),l,mid
#define rson rs(p),mid+1,r
const int N=1e5+5;
struct node{
	db mx;
	int ans;
}tr[N<<2];
int n,m;
int find(int p,int l,int r,db v){
	if(v>=tr[p].mx) return 0;
	if(l==r) return tr[p].mx>v;
	if(tr[ls(p)].mx<=v) return find(rson,v);
	else return tr[p].ans-tr[ls(p)].ans+find(lson,v);
}
void pushup(int p,int l,int r){
	tr[p].mx=max(tr[ls(p)].mx,tr[rs(p)].mx);
	tr[p].ans=tr[ls(p)].ans;
	db cmp=tr[ls(p)].mx;
	tr[p].ans+=find(rson,cmp);
}
void update(int p,int l,int r,int x,db v){
	if(l==r){tr[p].mx=v,tr[p].ans=1;return;}
	if(x<=mid) update(lson,x,v);
	else update(rson,x,v);
	pushup(p,l,r);
}
void build(int p,int l,int r){
	tr[p].ans=tr[p].mx=0;
	if(l==r) return;
	build(lson),build(rson);
}
int main(){
	cin>>n>>m;
	build(1,1,n);
	for(int i=1;i<=m;i++){
		int x,y;cin>>x>>y;
		update(1,1,n,x,1.0*y/x);
		cout<<tr[1].ans<<"\n";
	}
	return 0;
}
posted @ 2026-10-30 14:54  Rainesxiao  阅读(2)  评论(0)    收藏  举报