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

来源:互联网 发布:网络拓扑图算法 编辑:程序博客网 时间:2024/06/03 05:19

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

区间更新是指更新某个区间内的叶子节点的值,因为涉及到的叶子节点不止一个,而叶子节点会影响其相应的非叶父节点,那么回溯需要更新的非叶子节点也会有很多,如果一次性更新完,操作的时间复杂度肯定不是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 51 2 3 4 5 6 7 8 9 10Q 4 4Q 1 10Q 2 4C 3 6 3Q 2 45 101 2 3 4 5C 3 5 2Q 3 5*/
0 0