线段树,从入门倒退坑-贰

此文章介绍的均为加法操作

前言:

上篇文章介绍了线段树的基础操作。

其中区间修改用递归的方式一层层修改(类似于线段树的建立),但这样的时间复杂度比较高。

所以这篇文章引入一个新概念:懒惰标记。

今天也要加油哦!😙

Part 3:懒惰标记(lazy_tag)

Part 3.1:概念引入

使用懒惰标记,对于那些正好是线段树节点的区间,我们不继续递归下去,而是打上一个标记,将来要用到它的子区间的时候,再向下传递。

Part 3.2:分析

首先定义一个标记数组:tag[这里的下标和线段树一样]

表示这一段区间打上的标记。

接下来介绍标记的几种操作。

Part 3.2.1: 标记的传递

对于区间 llrr,懒惰标记为 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:线段树进阶

留到下次再讲吧😄