zoukankan      html  css  js  c++  java
  • POJ

    You have N integers, A1, A2, … , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.

    Input
    The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
    The second line contains N numbers, the initial values of A1, A2, … , AN. -1000000000 ≤ Ai ≤ 1000000000.
    Each of the next Q lines represents an operation.
    “C a b c” means adding c to each of Aa, Aa+1, … , Ab. -10000 ≤ c ≤ 10000.
    “Q a b” means querying the sum of Aa, Aa+1, … , Ab.

    Output
    You need to answer all Q commands in order. One answer in a line.

    Sample Input
    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
    Sample Output
    4
    55
    9
    15
    Hint
    The sums may exceed the range of 32-bit integers.

    题意:区间更新,区间求和模板题。
    输入操作Q 查询【L,R】区间内所有数之和
    输入操作C 区间【L,R】内的值增长c

    经典的线段树区间更新区间查询题目。区间更新采用延时标记节约时间。
    并利用push_down函数将mark【或者称为lazy】的延时标记向更小的区间更新。
    至于传说中的push_up操作,其实在单点更新的时候就存在,就一句,build或Update递归更新建树之后,回头将左右儿子节点的值传递回当前结点。也就是将以下代码单独写成函数使用:

    tre[rt].sum=tre[rt<<1].sum+tre[rt<<1|1].sum;

    这句话或是求最值之类的操作单独写出一个函数。
    区间更新的重点还是在push_down操作上。
    那么如何理解这个延时标记呢?
    首先我们对于更新的操作,单点时是一直更新到叶子节点,因为是单点。区间最小,必须经过logn的复杂度,如果是区间更新,所有值都看作单点更新,将是m*logn,这样的复杂度不如数组遍历更新来得快。
    因此我们在线段树中只需更新某个区间的值即可达到区间更新的效果,这样不需更新到叶子节点,而且一次更新即更新整个区间,非常方便,甚至小于一个单点更新的复杂度。
    那么问题来了,我们如果只做了某个区间的更新,没有落实到叶子上,那么下次查询其他区间时,与这次更新的区间相交,我们如何单独取出一段被更新的区间值来查询。这时就将我们之前更新的那个区间【也就是某个节点】的更新值向下传递,到真正用到这个更新时,我们才将其落到实处。
    那么简单,向上传递我们知道是子节点上传给当前结点。
    而向下传递即当前结点的更新至下传给两个子节点

     tre[rt<<1].sum+=(tre[rt<<1].r-tre[rt<<1].l+1)*1LL*mark[rt];
     tre[rt<<1|1].sum+=(tre[rt<<1|1].r-tre[rt<<1|1].l+1)*1LL*mark[rt];

    然后,因为我们有一个临时记录某个节点更新到一半的数组mark【或是lazy】
    【注意,此处的lazy和max和sum一样,都是每个节点有一个这样的属性,我们可以单独分出数组来存储,也能直接放到结构体中表示当前结点的被更新值的状态】
    这个临时记录更新值的延时标记,是用于给子节点传递更新值的。
    那么我们把当前结点的更新值已经传给子节点了,当前结点的更新值就没了,可以直接抹去更新,更改为0,注意,延时标记只是在查询,或更新时用到,因为我们对于一个查询和更新,一旦与之前延时的更新区间交错,就必须将之前的延时向下传递。这样才方便后面的统计和更新。
    那么我们再偷个懒,如果我们用到的区间,与之前更新的区间交错的位置,仍然不是很小呢?
    说明我们用到的延时更新没有具体到更小区间,我们也不用将其彻底更新到底,也就是说,这个延时我们发现不能再延了,必须马上将之前的更新真正给下面的小区间,但是我们仍然可以不用直接给到所有小区间,我们就给下一个小区间,或者下下一个小区间。直到足够我们要使用的那个区间即可。

    于是更新完当前结点的子节点的sum值后,我们还需将mark值给子节点。这个mark值可能给子节点继续用,继续向下传递。在push_down中就有了以下操作:

     mark[rt<<1]+=mark[rt];
     mark[rt<<1|1]+=mark[rt];
     mark[rt]=0;

    这就是push_down的全部内容,有了这两个函数,我们在区间更新时可以像区间查询那样直接调用。起到“半更新”的高效率。
    注意lazy或mark和sum或max一样,都严格需要初始化,否则对结果由严重影响。

    #include<stdio.h>
    #include<string.h>
    #define LL long long
    using namespace std;
    const int maxn=1e5+9;
    int n,q;
    LL a[maxn],mark[maxn<<2],ans;
    struct node
    {
        int l,r;
        LL sum;
    } tre[maxn<<2];
    void pushup(int rt)
    {
        tre[rt].sum=tre[rt<<1].sum+tre[rt<<1|1].sum;
    }
    void pushdown(int rt)///向下传递,根据结点的区间边界对sum求和与mark延时标记进行更新
    {
        if(mark[rt])
        {
            tre[rt<<1].sum+=(tre[rt<<1].r-tre[rt<<1].l+1)*1LL*mark[rt];
            tre[rt<<1|1].sum+=(tre[rt<<1|1].r-tre[rt<<1|1].l+1)*1LL*mark[rt];
            mark[rt<<1]+=mark[rt];
            mark[rt<<1|1]+=mark[rt];
            mark[rt]=0;
        }
    }
    void build(int l,int r,int rt)
    {
        tre[rt].l=l;
        tre[rt].r=r;
        if(l==r) tre[rt].sum=a[l];
        else
        {
            int mid=(l+r)>>1;
            build(l,mid,rt<<1);
            build(mid+1,r,rt<<1|1);
            pushup(rt);
        }
    }
    void update(int l,int r,LL val,int rt)
    {
        if(tre[rt].l>=l&&tre[rt].r<=r)
        {
            tre[rt].sum+=(tre[rt].r-tre[rt].l+1)*val;///更新区间时要加上长度*val
            mark[rt]+=val;///并且对mark延时标记更新
        }
        else
        {
            pushdown(rt);///查询及更新结点时要先判断该位置是否有延时标记,如果有则push_down向下传递
            int mid=(tre[rt].l+tre[rt].r)>>1;
            if(l>mid)update(l,r,val,rt<<1|1);
            else if(r<=mid) update(l,r,val,rt<<1);
            else
            {
                update(l,r,val,rt<<1|1);
                update(l,r,val,rt<<1);
            }
            pushup(rt);///更新后向上传递
        }
    }
    void query(int l,int r,int rt)
    {
        if(tre[rt].l>=l&&tre[rt].r<=r) ans+=tre[rt].sum;
        else
        {
            pushdown(rt);///查询向下传递
            int mid=(tre[rt].l+tre[rt].r)>>1;
            if(l>mid)query(l,r,rt<<1|1);
            else if(r<=mid)query(l,r,rt<<1);
            else
            {
                query(l,r,rt<<1|1);
                query(l,r,rt<<1);
            }
        }
    }
    int main()
    {
        while(scanf("%d%d",&n,&q)!=EOF)
        {
            memset(mark,0,sizeof mark);
            for(int i=1; i<=n; i++) scanf("%lld",&a[i]);
            build(1,n,1);
            char com[3];
            int l,r;
            LL val;
            while(q--)
            {
                ans=0;
                scanf("%s",com);
                if(com[0]=='Q')
                {
                    scanf("%d%d",&l,&r);
                    query(l,r,1);
                    printf("%lld
    ",ans);
                }
                else
                {
                    scanf("%d%d%lld",&l,&r,&val);
                    update(l,r,val,1);
                }
            }
        }
    }
    
  • 相关阅读:
    背完这444句,你的口语绝对不成问题了
    过滤HTML
    Asp.net页面的生命周期
    查询分组中的前几条记录
    offsetLeft,Left,clientLeft的区别
    可以用javascript实现的10种图片特效
    了解黑客经常使用哪些工具
    js日历控件
    asp.net中的path备忘录
    ASP.NET MVC3 向View传递数据
  • 原文地址:https://www.cnblogs.com/kuronekonano/p/11135774.html
Copyright © 2011-2022 走看看