线段树模板与讲解

线段树

示例代码

#include <bits/stdc++.h>
using namespace std;

#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

const int maxn=1000010;

int n,q;
long long sum[maxn<<2],lazy[maxn<<2];

void Pushup(int rt){
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt){
  //l和r表示当前节点的区间左右端点,rt表示当前节点在线段树中的编号。
    if(l==r){
        scanf("%lld",&sum[rt]);
        return;
    } 
    int m=(l+r)>>1;//右移1位相当于除以2,
    build(lson);
    build(rson);
    Pushup(rt); 
}
void update(int L,int R,int v,int l,int r,int rt){
  //L和R表示需要更新的区间左右端点,v表示需要更新的值,l和r表示当前节点的区间左右端点,rt表示当前节点在线段树中的编号。
    if(L<=l&&r<=R){
        sum[rt]+=(r-l+1)*v; // 更新区间和时需要乘以区间长度(r-l+1)
        lazy[rt]+=v; // 将更新操作记录在lazy数组中
        return;
    }
    pushdown(lson); // 下推标记到子节点
    pushdown(rson); // 下推标记到子节点
    int m=(l+r)>>1;
    if(L<=m) update(L,R,v,lson); // 左子树更新操作
    if(m<R) update(L,R,v,rson); // 右子树更新操作
    Pushup(rt); // 更新当前节点的值
}
void pushdown(int l,int r,int rt){ // 
  //l和r表示当前节点的区间左右端点,rt表示当前节点在线段树中的编号。
    if(lazy[rt]!=0){ // 如果存在延迟标记,则对左右子节点进行更新操作,并将延迟标记清零
        lazy[rt<<1]+=lazy[rt]; // 将延迟标记传递到左子节点
        lazy[rt<<1|1]+=lazy[rt]; // 将延迟标记传递到右子节点
        sum[rt<<1]+=(r-(l+1)+1)*lazy[rt]; // 更新左子节点的区间和,并加上延迟标记值乘以区间长度(r-(l+1)+1)
        sum[rt<<1|1]+=(r-(l+1)+1)*lazy[rt]; // 更新右子节点的区间和,并加上延迟标记值乘以区间长度(r-(l+1)+1)
        lazy[rt]=0; // 将当前节点的延迟标记清零,表示该节点已经处理过延迟标记了
    }
}
long long query(int L,int R,int l,int r,int rt){
  //L和R表示需要查询的区间左右端点,l和r表示当前节点的区间左右端点,rt表示当前节点在线段树中的编号。
    pushdown(l,r,rt); // 下推标记到子节点并处理延迟标记的操作函数
    if(L<=l&&r<=R){ // 如果查询区间完全包含当前节点区间,则直接返回当前节点的值即可。
        return sum[rt];
    }else{ // 如果查询区间不完全包含当前节点区间,则需要继续向下查询子节点。
        int m=(l+r)>>1;
        long long res=0; 
        if(L<=m) res+=query(L,R,lson); // 左子树查询操作,如果查询区间与左子树有交集,则递归查询左子树。否则返回0。
        if(m<R) res+=query(L,R,rson); // 右子树查询操作,如果查询区间与右子树有交集,则递归查询右子树。否则返回0。
        return res; // 返回查询结果。
  }
}

lasy标记

在代码中,lazy标记的作用是记录区间更新操作的延迟标记,即记录需要对区间进行更新的操作,但是并不立即执行该更新操作。这样可以避免重复计算和多次遍历,提高程序的运行效率。

具体来说,当需要对一个区间进行更新操作时,可以将更新操作的延迟标记保存在lazy数组中,并将该区间的所有子节点也标记为需要进行更新操作的状态。然后,在后续的查询操作中,如果发现某个节点有延迟标记,就将其下推到其左右子节点中,并处理延迟标记。这样,就可以将原本需要在每个节点上进行的更新操作转化为只在叶子节点上进行的更新操作,从而减少重复计算和多次遍历的情况。

build函数

build函数是线段树的构建函数,其工作原理如下:

首先判断当前区间是否只有一个元素,如果是则直接将该元素的值存入sum数组中并返回。

如果当前区间有多个元素,则计算中间位置m=(l+r)>>1m=(l+r)>>1m=(l+r)>>1,将当前区间分成两个子区间[l,m][l,m][l,m][m+1,r][m+1,r][m+1,r]

递归调用build函数对左子区间[l,m][l,m][l,m]进行构建,得到左子树的根节点编号为rt<<1。

递归调用build函数对右子区间[m+1,r][m+1,r][m+1,r]进行构建,得到右子树的根节点编号为rt<<1∣1rt<<1|1rt<<1∣1

最后调用Pushup函数更新当前节点的值,即sum[rt]=sum[rt<<1]+sum[rt<<1∣1]sum[rt]=sum[rt<<1]+sum[rt<<1|1]sum[rt]=sum[rt<<1]+sum[rt<<1∣1]

通过以上步骤,可以构建出完整的线段树。在构建过程中,每个节点的值都可以通过其左右子节点的值相加得到,从而实现快速查询区间和的功能。

update函数

update函数是线段树的更新函数,其工作原理如下:

首先判断需要更新的区间是否完全包含当前节点的区间,如果是则直接将更新操作记录在lazy数组中,并返回。

如果需要更新的区间不完全包含当前节点的区间,则将延迟标记下推到左右子节点中,并处理延迟标记。这样可以将原本需要在每个节点上进行的更新操作转化为只在叶子节点上进行的更新操作,从而减少重复计算和多次遍历的情况。

计算中间位置m=(l+r)>>1m=(l+r)>>1m=(l+r)>>1,如果需要更新的区间与左子区间有交集,则递归调用update函数对左子区间进行更新操作;如果需要更新的区间与右子区间有交集,则递归调用update函数对右子区间进行更新操作。

最后调用Pushup函数更新当前节点的值。

通过以上步骤,可以实现对线段树中的某个区间进行更新操作。在更新过程中,每个节点的值都可以通过其左右子节点的值相加得到,从而实现快速查询区间和的功能。

query函数

首先调用pushdown函数将当前节点的延迟标记下推到左右子节点中,并处理延迟标记。这样可以确保子节点的延迟标记被正确处理。

判断需要查询的区间是否完全包含当前节点的区间,如果是则直接返回当前节点的值即可。注意这里不需要再调用pushdown函数了。

如果需要查询的区间不完全包含当前节点的区间,则需要继续向下查询子节点。计算中间位置m=(l+r)>>1m=(l+r)>>1m=(l+r)>>1,如果需要查询的区间与左子区间有交集,则递归调用query函数对左子区间进行查询操作;如果需要查询的区间与右子区间有交集,则递归调用query函数对右子区间进行查询操作。

最后将左右子节点的查询结果相加得到当前节点的查询结果,并返回。

通过以上步骤,可以实现对线段树中的某个区间进行查询操作。在查询过程中,每个节点的值都可以通过其左右子节点的值相加得到,从而实现快速查询区间和的功能。

Pushup 函数

pushup函数则是用于更新当前节点的值,即将左右子节点的值相加得到当前节点的值。这样可以实现快速查询区间和的功能。

Pushdown 函数

下推标记到子节点并处理延迟标记的操作函数。pushdown函数用于将当前节点的延迟标记下推到其左右子节点中,并处理延迟标记。这样可以将原本需要在每个节点上进行的更新操作转化为只在叶子节点上进行的更新操作,从而减少重复计算和多次遍历的情况。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值