单侧递归线段树
简介
线段树最重要的操作就是 pushup 和 pushdown,对于一类问题,光凭当前节点的信息无法合并,需要用到它的子节点的信息才能合并,这就是线段树单侧递归问题,时间复杂度一般是 \(O(nlog^2n)\) 。
通常解决区间前缀最值问题
例题
我们先将楼转化成斜率,那么这道题相当于求前缀最大值的个数,我们考虑如何上传
定义 \(find(p,v)\) 为前缀最大已为 \(v\),在 \(p\) 节点所代表区间的中前缀最大的个数,那么显然有 \(ans_p=ans_{ls}+find(rs,max_{ls})\)(\(max\) 代表区间最值)
我们考虑分类讨论求 \(find\)
- \(l=r\),只有一个节点,直接判断即可
- \(max_{p}\le v\),整个区间不可能有最大值,返回0
- \(max_{ls}\le v\),左边不可能,递归右边(如图)
![image.png]()
- \(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;
}



浙公网安备 33010602011771号