线段树模板

线段树讲解:https://blog.csdn.net/huangzihaoal/article/details/81813454
线段树模板:
数组开4*maxn

const int N=4000005;
int init;
struct node
{
    int l/*区间左边界*/,r/*区间右边界*/,sum/*区间元素之和*/,lazy/*懒惰标记,下文会提到*/;
    node()
    {
        l=r=sum=lazy=0;   //给每一个元素赋初值
    }
} a[N]; //N为总节点数
inline void update(int k)//更新节点k的sum
{
    a[k].sum=a[k*2].sum+a[k*2+1].sum;
    //很显然,一段区间的元素和等于它的子区间的元素和
    //如果有懒惰标记的话要相应地改变
}
void build(int k/*当前节点的编号*/,int l/*当前区间的左边界*/,int r/*当前区间的右边界*/)
{
    a[k].l=l,a[k].r=r;
    if(l==r)//递归到叶节点
    {
        a[k].sum=init;//其中init为给定的初值
        return;
    }
    int mid=(l+r)/2;//计算左右子节点的边界
    build(k*2,l,mid);//递归到左儿子
    build(k*2+1,mid+1,r);//递归到右儿子
    update(k);//记得要用左右子区间的值更新该区间的值
}
void change(int k/*当前节点的编号*/,int x/*要修改节点的编号*/,int y/*要把编号为x的数字修改成y*/)
{
    if(a[k].l==a[k].r)
    {
        a[k].sum=y;
        return;
    }
    //如果当前区间只包含一个元素,那么该元素一定就是我们要修改的。
    //由于该区间的sum一定等于编号为x的数字,所以直接修改sum就可以了。
    int mid=(a[k].l+a[k].r)/2;//计算下一层子区间的左右边界
    if(x<=mid)
        change(k*2,x,y);//递归到左儿子
    else
        change(k*2+1,x,y);//递归到右儿子
    update(k);//记得更新点k的值,感谢qq_36228735提出此错误
}
void pushdown(int k)//将点k的懒惰标记下传
{
    if(a[k].l==a[k].r)
    {
        a[k].lazy=0;
        return;
    }
    //如果节点k已经是叶节点了,没有子节点,那么标记就不用下传,直接删除就可以了
    a[k*2].sum+=(a[k*2].r-a[k*2].l+1)*a[k].lazy;
    a[k*2+1].sum+=(a[k*2+1].r-a[k*2+1].l+1)*a[k].lazy;
    //给k的子节点重新赋值
    a[k*2].lazy+=a[k].lazy;
    a[k*2+1].lazy+=a[k].lazy;
    //下传点k的标记
    a[k].lazy=0;//记得清空点k的标记
}
void changeSegment(int k,int l,int r,int x)
//当前到了编号为k的节点,要把[l..r]区间中的所有元素的值+x
{
    if(a[k].l==l&&a[k].r==r)//如果找到了全部元素都要被修改的区间
    {
        a[k].sum+=(r-l+1)*x;
        //更新该区间的sum
        a[k].lazy+=x;
        return;
        //懒惰标记叠加
    }
    int mid=(a[k].l+a[k].r)/2;
    pushdown(k);
    if(r<=mid)
        changeSegment(k*2,l,r,x);
    //如果被修改区间完全在左区间
    else if(l>mid)
        changeSegment(k*2+1,l,r,x);
    //如果被修改区间完全在右区间
    else
        changeSegment(k*2,l,mid,x),changeSegment(k*2+1,mid+1,r,x);
    //如果都不在,就要把修改区间分解成两块,分别往左右区间递归
    update(k);
    //记得更新点k的值
}
int query(int k,int l,int r)
//当前到了编号为k的节点,查询[l..r]的和
{
    if(a[k].lazy)
        pushdown(k);
    //如果当前节点被打上了懒惰标记,那么就把这个标记下传,这一句其实也可以放在下一语句的后面
    if(a[k].l==l&&a[k].r==r)
        return a[k].sum;
    //如果当前区间就是询问区间,完全重合,那么显然可以直接返回
    int mid=(a[k].l+a[k].r)/2;
    if(r<=mid)
        return query(k*2,l,r);
    //如果询问区间包含在左子区间中
    if(l>mid)
        return query(k*2+1,l,r);
    //如果询问区间包含在右子区间中
    return query(k*2,l,mid)+query(k*2+1,mid+1,r);
    //如果询问区间跨越两个子区间
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值