介绍:
过多就不赘述了
它与树状数组相比,更加灵活,但码量大,容易写错。
它每一个节点代表一个区间。
每一个节点分左端和右端。
只有唯一的根结点。
叶子节点代表一个数,或者长度为1的区间。
对于每一个节点(区间【l,r】)左节点为【l,mid】,右节点为【mid+1,r】。
实际上除去所有叶子节点,它就是一个完全二叉树
假设它是一个满二叉树,它所要开的区间要为 4N 以上,防止越界。
过程:
建树-
struct node{
long l,r;
long d;
}t[4*N];
inline void build(long p,long l,long r){
t[p].l=l,t[p].r=r;
if(t[p].l==t[p].r) {
t[p].d=a[l];
return ;
}
long mid=(l+r)>>1;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
t[p].d=max(t[p*2].d,t[p*2+1].d);
}
// build(1,1,n); // 其中t[p].d为区间最大值
单点修改:
inline void change(long p,long x,long z){
if(t[p].l==t[p].r) {
a[p].d=z;
return ;
}
long mid=(t[p].l+t[p].r)>>1;
if(x<=mid) change(p*2,x,z);
else change(p*2+1,x,z);
t[p].d=max(t[p*2].d,t[p*2+1].d);
}
//change(1,x,z);
区间查询:
long ask(long p,long l,long r){
if(l<=t[p].l && r>=t[p].r) return t[p].d;
long mid=(t[p].l+t[p].r)>>1;
long minn=-(1<<30);
if(l<=mid) minn=max(minn,ask(p*2,l,r));
if(r>mid) minn=max(minn,ask(p*2+1,l,r));
return minn;
}
// long answer=ask(1,l,r);
这个过程需要自己模拟一遍就懂了
详细不再赘述。
区间修改:
inline void _change(long p,long l,long r,long z){
if(l<=t[p].l && r>=t[p].r) {
t[p].sum+=(t[p].r-t[p].l+1)*z;
return ;
}
if(l<=mid) _change(p*2,l,r,z);
if(r>mid) _change(p*2+1,l,r,z);
t[p].sum=t[p*2].sum+t[p*2+1].sum;
}
懒惰标记(延迟标记,lazy):
因为我们每一次进行区间修改,如果每一个点都去一个个地更新,复杂度相对较高,而且我们其实可以发现,在后续查询中,不一定会用到这些辛辛苦苦修改过的点,所以我们可以先给这个点打上标记,记录修改多少,记住自身应先修改,然后用到它的子树的时候再将标记下推。
inline void spread (long p){
if(t[p].lazy){
t[p*2].sum+=t[p].lazy*(t[p*2].r-t[p*2].l+1);
t[p*2+1].sum+=t[p].lazy*(t[p*2+1].r-t[p*2+1].l+1);
t[p*2].lazy+=t[p].lazy;
t[p*2+1].lazy+=t[p].lazy;
t[p].lazy=0;
}
}
inline void _change(long p,long l,long r,long z){
if(......){
......;
t[p].lazy+=z;
return ;
}
spread(p);
......
}
long ask(long p,long l,long r){
if(......)......;
spread(p);
......;
}
扫描线:
......