自学笔记-线段树(21.8.9)

Posted 未定_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自学笔记-线段树(21.8.9)相关的知识,希望对你有一定的参考价值。

线段树(一)
三.区间修改
1.在线段树的基础上增加了以下操作:
•区间[i,j]内的值全部加v
•计算区间[l,r]的区间和
为了节省时间,采取lazy原理,即树上某一结点的区间全部在[i,j]范围内,只需要对该结点的区间值全部加v,不需要继续深入对每一个数改变;如果树上某一结点的区间有部分在[i,j]范围内,不能再用lazy,需继续深入直到全部在范围内。图解:以1~10为例,把区间[3,6]每个元素加3。

为了用lazy原理,需要增加一个数组add[i]来记录,其值为每个元素需要增加的值,多次lazy也可以累加。如果某结点lazy被破坏,则需要深入(向下更新),该结点add归0,表示没有lazy,更新子结点,这里通常自写向下更新函数push_down( )实现该功能。

2.建树: 可以用满二叉树建树:以1~10为例

结点上方区间和用sum[]表示,右上方红色记录是否用到lazy原理,即add[]

void build(int l,int r,int rt)//满二叉树建树
{
    add[rt]=0;
    if(l==r)
    {
        scanf("%lld",&sum[rt]);
        return;
    }
    int mid=(l+r)>>1;
    build(lson);
    build(rson);
    push_up(rt);//向上更新区间和
}

3.向上更新函数与向下更新函数
向上更新函数用于把子结点的值递归到父结点,更新区间并求区间和时需要用到;向下更新函数用于碰坏lazy,更新子结点。

void push_up(int rt)//向上更新,通过当前结点rt把值递归到父结点
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void push_down(int rt,int m)//更新rt的子结点,m为区间数字的个数
{
    if(add[rt])
    {
        add[rt<<1]+=add[rt];//左儿子累加
        add[rt<<1|1]+=add[rt];//右儿子累加
        sum[rt<<1]+=(m-(m>>1))*add[rt];//左儿子区间总和变化
        sum[rt<<1|1]+=(m>>1)*add[rt];//右儿子区间总和变化
        add[rt]=0;//取消本层标记
    }
}

4.区间更新
对区间[a,b]内的每一个元素加c,[l,r]为查询区间

void update(int a,int b,long long c,int l,int r,int rt)
{
    if(a<=l&&b>=r)
    {
        sum[rt]+=(r-l+1)*c;
        add[rt]+=c;
        return;
    }
    push_down(rt,r-l+1);//先向下更新
    int mid=(l+r)>>1;//分成两半继续深入
    if(a<=mid)
        update(a,b,c,lson);
    if(b>mid)
        update(a,b,c,rson);
    push_up(rt);//向上更新
}

以更改[3,6]为例


5.区间求和

long long query(int a,int b,int l,int r,int rt)//区间求和
{
    if(a<=l&&b>=r)
        return sum[rt];//满足lazy,直接返回值
    push_down(rt,r-l+1);//向下更新
    int mid=(l+r)>>1;
    long long ans=0;
    if(a<=mid)
        ans+=query(a,b,lson);
    if(b>mid)
        ans+=query(a,b,rson);
    return ans;
}

以查询区间[2,4]的和为例:

经典模板题:A Simple Problem with Integers
题意:给你N个数,进行Q个操作
操作1:C a b c:[a,b]内每个元素加c
操作2:Q a b:求:[a,b]区间和

#include<iostream>
#include<cstdio>
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1//这里或运算即rt*2+1
using namespace std;
const int MAXN=1e5+10;
long long sum[MAXN<<2],add[MAXN<<2];//sum[]记录结点的区间和,add[]记录结点是否用到了lazy原理,数组都需要开4倍空间
void push_up(int rt)//向上更新,通过当前结点rt把值递归到父结点
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void push_down(int rt,int m)//更新rt的子结点,m为区间数字的个数
{
    if(add[rt])
    {
        add[rt<<1]+=add[rt];//左儿子累加c
        add[rt<<1|1]+=add[rt];//右儿子累加c
        sum[rt<<1]+=(m-(m>>1))*add[rt];//左儿子区间总和变化
        sum[rt<<1|1]+=(m>>1)*add[rt];//右儿子区间总和变化
        add[rt]=0;//取消本层标记
    }
}
void build(int l,int r,int rt)//满二叉树建树
{
    add[rt]=0;
    if(l==r)
    {
        scanf("%lld",&sum[rt]);
        return;
    }
    int mid=(l+r)>>1;
    build(lson);
    build(rson);
    push_up(rt);//向上更新区间和
}
void update(int a,int b,long long c,int l,int r,int rt)
{
    if(a<=l&&b>=r)
    {
        sum[rt]+=(r-l+1)*c;
        add[rt]+=c;
        return;
    }
    push_down(rt,r-l+1);//先向下更新
    int mid=(l+r)>>1;//分成两半继续深入
    if(a<=mid)
        update(a,b,c,lson);
    if(b>mid)
        update(a,b,c,rson);
    push_up(rt);//向上更新
}
long long query(int a,int b,int l,int r,int rt)//区间求和
{
    if(a<=l&&b>=r)
        return sum[rt];//满足lazy,直接返回值
    push_down(rt,r-l+1);//向下更新
    int mid=(l+r)>>1;
    long long ans=0;
    if(a<=mid)
        ans+=query(a,b,lson);
    if(b>mid)
        ans+=query(a,b,rson);
    return ans;
}
int main()
{
    int N,Q;
    scanf("%d%d",&N,&Q);
    build(1,N,1);
    while(Q--)
    {
        char str[2];//为了空格不影响后面操作
        scanf("%s",str);
        int a,b;
        long long c;
        if(str[0]=='C')
        {
            scanf("%d%d%lld",&a,&b,&c);
            update(a,b,c,1,N,1);
        }
        else{
            scanf("%d%d",&a,&b);
            printf("%lld\\n",query(a,b,1,N,1));
        }
    }
    return 0;
}

以上是关于自学笔记-线段树(21.8.9)的主要内容,如果未能解决你的问题,请参考以下文章

数据结构零基础线段树笔记1

数据结构线段树笔记2

线段树笔记的补充(一些不太一样的线段树)

线段树笔记的补充(一些不太一样的线段树)

线段树详解

线段树学习笔记