此文章介绍的均为加法操作
前言:
上篇文章介绍了线段树的基础操作。
其中区间修改用递归的方式一层层修改(类似于线段树的建立),但这样的时间复杂度比较高。
所以这篇文章引入一个新概念:懒惰标记。
今天也要加油哦!😙
Part 3:懒惰标记(lazy_tag)
Part 3.1:概念引入
使用懒惰标记,对于那些正好是线段树节点的区间,我们不继续递归下去,而是打上一个标记,将来要用到它的子区间的时候,再向下传递。
Part 3.2:分析
首先定义一个标记数组:tag[这里的下标和线段树一样]
表示这一段区间打上的标记。
接下来介绍标记的几种操作。
Part 3.2.1: 标记的传递
对于区间 到 ,懒惰标记为 tag[p]
。
分别传递到左右区间的标记:tag[p<<1]+=tag[p];tag[p<<1|1]+=tag[p];
。
再将左右区间的值改变:ans[p<<1]+=tag[p]*(mid-l+1);ans[p<<1|1]+=tag[p]*(r-mid);
。
最后删除原来的标记:tag[p]=0;
。
Part 3.2.1:结合
在区间求和和区间修改的每次分治前下放标记。
区间修改时要再加上标记即可。
Part 3.3:代码总结
inline ll ls(ll x){
return x<<1;
}
inline ll rs(ll x){
return x<<1|1;
}
inline void push_up(ll p){
ans[p]=ans[ls(p)]+ans[rs(p)];
}
void build(ll p,ll l,ll r){
tag[p]=0;
if(l==r){ans[p]=a[l];return ;}
ll mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
push_up(p);
}
inline void f(ll p,ll l,ll r,ll k){
tag[p]=tag[p]+k;
ans[p]=ans[p]+k*(r-l+1);
}
inline void push_down(ll p,ll l,ll r){
ll mid=(l+r)>>1;
f(ls(p),l,mid,tag[p]);
f(rs(p),mid+1,r,tag[p]);
tag[p]=0;
}
inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k){
if(nl<=l&&r<=nr){
ans[p]+=k*(r-l+1);
tag[p]+=k;
return ;
}
push_down(p,l,r);
ll mid=(l+r)>>1;
if(nl<=mid)update(nl,nr,l,mid,ls(p),k);
if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
push_up(p);
}
ll query(ll q_x,ll q_y,ll l,ll r,ll p){
ll res=0;
if(q_x<=l&&r<=q_y)return ans[p];
ll mid=(l+r)>>1;
push_down(p,l,r);
if(q_x<=mid)res+=query(q_x,q_y,l,mid,ls(p));
if(q_y>mid) res+=query(q_x,q_y,mid+1,r,rs(p));
return res;
}
Part 4:线段树进阶
留到下次再讲吧😄