线段树区间更新(以POJ 3468为例)

本文详细介绍了如何利用线段树实现区间更新的功能,通过引入延迟标记的概念来优化时间复杂度,达到高效处理区间更新的目的。文章提供了具体的实现代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在了解单点更新的线段树的前提下,再继续理解区间更新的线段树。

区间更新是指更新某个区间内的叶子节点的值,因为涉及到的叶子节点不止一个,而叶子节点会影响其相应的非叶父节点,那么回溯需要更新的非叶子节点也会有很多,如果一次性更新完,操作的时间复杂度肯定不是O(lgn)。为此引入了线段树中的延迟标记概念,这也是线段树的精华所在。

延迟标记:每个节点新增加一个标记,记录该节点是否进行了某种修改。将指定更新区间在树上找到对应节点后,给这些节点打上标记。在查询时,如果当前节点有标记,就给它的子节点打上该标记,并删除当前节点的标记。
延迟标记除了能够记录某个节点的是否进行修改外,通常也记录修改的情况,方便在查询时直接能够得到修改的结果。

struct Node{
    ll l,r;
    ll sum,add;
}

对于区间更新的线段树,通常有四个功能:
build:左右递归建树,初始化每个节点sum值。同时将每个节点的延迟标记设置为0。
pushDown:将父节点的延迟标记传递给子节点(如果题意需要,还要将父节点的值传给子节点),同时将父节点的延迟标记清空。
query:对指定区间查询,左右子树递归查询。查询时如果父节点有延迟标记,需要向下传递。
update:对指定区间更新,左右子树递归更新。当更新到指定区间时,加上父节点的延迟标记(如果题意需要,还要进行其他操作),返回。如果没有更新到指定区间,就继续更新,如果过程中有节点有延迟标记,需要向下传递。最后回溯更新父节点的值。

注意:

update时,只有指定区间会有延迟标记,并且此时指定区间和它的父区间的sum已经更改,它的子区间和其他区间没有延迟标记和修改值。
query时,延迟标记会传至子区间,并且同时修改子区间的值,直到找到指定区间。此时,延迟标记在指定区间上,指定区间的子区间的sum值没有修改。

对于POJ 3468:
在pushDown和update时,子区间需要加上父区间传过来的值*区间元素数;
(具体操作见代码)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll a[N];
char opr[10];
struct Node{
    ll l,r;
    ll sum,add;
}tree[4*N];
void build(ll i,ll l,ll r){
    tree[i].l=l,tree[i].r=r;
    tree[i].add=0;
    if(l==r){//左边=右边,单点建立
        tree[i].sum=a[l];
        return;
    }
    ll tmp=i<<1;
    ll mid=(l+r)>>1;
    build(tmp,l,mid);//左子树
    build(tmp+1,mid+1,r);//右子树
    tree[i].sum=tree[tmp].sum+tree[tmp+1].sum;//回溯得到区间sum值
}
void pushDown(ll x){
    ll tmp=x<<1;
    tree[tmp].add+=tree[x].add;//左子树得到父节点标记
    tree[tmp+1].add+=tree[x].add;//右子树得到父节点标记

    //如果区间内每个元素加上一个值,那么这个区间会加上当前区间包含指定区间元素个数*待加元素的值
    tree[tmp].sum+=tree[x].add*(tree[tmp].r-tree[tmp].l+1);
    tree[tmp+1].sum+=tree[x].add*(tree[tmp+1].r-tree[tmp+1].l+1);
    tree[x].add=0;//清空父节点标记
}
ll query(ll i,ll l,ll r){

    if(tree[i].l>r||tree[i].r<l){//超出范围 
        return 0;
    }
    if(tree[i].l>=l&&tree[i].r<=r){//刚好在落到求得的区间 
        return tree[i].sum;
    }
    //如果查询时发现当前区间不为目标区间,但是有延迟标记时,将延迟标记传递 
    if(tree[i].add) pushDown(i); 

    ll tmp=i<<1;


    //左右子树分别查询,将查询结果相加
    //如果某一子树超出范围,会在第一个条件处return 0
    return query(tmp,l,r)+query(tmp+1,l,r);


}
void update(ll i,ll l,ll r,ll s){
    if(tree[i].l>r||tree[i].r<l){//超出范围 
        return ;
    }

    //在指定区间的范围内,该点加上sum与延迟标记.此时包含该点的父区间均更新完毕 
    //>=、<=是考虑到区间位于不同子树的情况 

    if(tree[i].l>=l&&tree[i].r<=r){
        tree[i].add+=s;
        tree[i].sum+=s*(tree[i].r-tree[i].l+1);
        return;
    }
    //如果之前已经有延迟标记,需要将之前的标记处理 
    if(tree[i].add) pushDown(i); 
    ll tmp=i<<1;

    //更新左右子树,如果某个子树超出了所更新区间的范围,会在第一个条件处return 

    update(tmp,l,r,s);
    update(tmp+1,l,r,s);

    tree[i].sum=tree[tmp].sum+tree[tmp+1].sum;//回溯更新父节点的值   
}

int main(){
    int n,q;
    while(~scanf("%d %d",&n,&q)){
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        build(1,1,n);
        ll l,r,s;
        while(q--){
            scanf("%s",opr);
            if(opr[0]=='Q'){
                scanf("%lld %lld",&l,&r);
                cout<<query(1,l,r)<<endl; 

            }
            else if(opr[0]=='C'){
                scanf("%lld %lld %lld",&l,&r,&s);
                update(1,l,r,s);
            }
        }
    }
}
/*
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4

5 10
1 2 3 4 5
C 3 5 2
Q 3 5


*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值