线段树之区间更新

线段树之区间更新

   线段树系列上一篇文章讲了基础的线段树的建树,单点更新,区间查询。那这篇文章主要讲线段树的区间更新,也就是延迟更新。其实延迟更新的本质和单点更新差不多,只不过差别在于单点更新每次都递归到底,但是区间更新则是做一个延迟标记,等到下次更新或查询的时候再去判断是不是往下更新。

    为什么这样?答案是显然的。线段树的查询和单点更新的时间复杂度是O(logn)的,如果我每次都只是更新一个节点,再去询问,那么复杂度是O(nlogn),这个复杂度是可以接受的,但是如果每次操作我都是更新一个区间,还是用单点更新去做,那么复杂度是O(n^2logn),这个复杂度是很大的,一般来说,在ACM竞赛中是很难接受的或者说是直接摒弃的。所以我们要想办法去降低复杂度。于是我们就有了区间更新

    以下我们以给某个区间增加x为例讲解:

    假设我要更新区间[a,b],那我开始由根节点开始递归查找区间,如果发现区间[l,r]完全包含在区间[a,b]里,我就在这停止,不在继续更新下去,并在这个节点做标记记录x,并更新这个节点的sum值。如果下次查询的时候,碰见标记就开始更新左右儿子,并把自己的标记消除,给儿子做标记,以此类推,直到找到符合查找的区间为止。

     这样为什么会快,当节点数很多的时候,如果我每次都把整个操作区间更新到底就会很浪费时间,通过标记,我可以更新到某一层就停止,不必更新到底,这样当然就很节省时间了。

    我们就以这个题意编写代码讲解:

    题意:在一组数中执行两种操作:

      (1) "C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000.

      (2)"Q a b" means querying the sum of Aa, Aa+1, ... , Ab.

const int maxn=10005;//假设点的个数
struct seg_tree//为线段树定义一个结构体
{
    int sum,seg;//sum表示区间和,seg作为标记
}tree[4*maxn];

//更新父亲节点操作
void pushup(int root)
{
    tree[root].sum=tree[root<<1].sum+tree[root<<1|1].sum;
    return ;
}

//更新左右儿子操作
void pushdown(int root,int l,int r)
{
    int mid=(l+r)>>1;
    //更新左儿子
    tree[root<<1].seg+=tree[root].seg;
    tree[root<<1].sum+=(mid-l+1)*tree[root].seg;
    //更新右儿子
    tree[root<<1|1].seg+=tree[root].seg;
    tree[root<<1|1].sum+=(r-(mid+1)+1)*tree[root].seg;
    //消除父亲节点的标记
    tree[root].seg=0;

    return ;
}

//建树操作
void build_tree(int root,int r,int l)
{
    tree[root].seg=0;

    if(l==r)
    {
        tree[root].sum=A[l];
        return ;
    }

    int mid=(r+l)>>1;

    build_tree(root<<1,l,mid);
    build_tree(root<<1|1,mid+1,r);

    pushup(root);
}

//更新操作
void update(int root,int l,int r,int L,int R,int val )//L,R表示更新的区间的端点,val表示要加的值
{
     if(L<=l&&r<=R) //如果被操作区间完全包含,就不用往下更新了
     {
         tree[root].sum+=(r-l+1)*val;
         tree[root].seg+=val;
         return ;
     }

     if(tree[root].seg!=0)   {  pushdown(root,l,r);}//延迟更新

     int mid=(r+l)>>1;

     if(L<=mid)
        update(root<<1,l,mid,L,R,val);
     if(mid<R)
        update(root<<1|1,mid+1,r,L,R,val);

     pushup(root);  
}

//查询操作
int query(int root,int l,int r,int L,int R)//L,R表示查询的区间的端点
{
     if(L<=l&&r<=R)
     {
         return tree[root].sum;
     }

     if(tree[root].seg!=0)//如果有标记就需要往下更新
        pushdown(root,l,r);

     int mid=(r+l)>>1,sum=0;

     if(L<=mid)
         sum+=query(root<<1,l,mid,L,R);
     if(mid<R)
         sum+=query(root<<1|1,mid+1,r,L,R);

     return sum;
}


    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值