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.

题意:输入n个数字,有q次操作。操作种类有两种:
①输入C a b c,区间a到b内所有数增长c
②输入Q a b,查询区间a到b内所有值之和

这是一道线段树区间更新区间查询的模板题,用来练习树状数组的区间更新和区间查询。
树状数组一般只能做单点更新、单点查询或区间查询。此处利用差分法可以使树状数组做到区间更新区间查询。

首先我们定义一个值d【i】,表示a【i】-a【i-1】的差值,那么a【1】-a【0】仍等于a【1】,我们定义a【0】=0,而第一个元素是a【1】,那么d【1】=a【1】,在后续的d【i】中,都表示一个差值。

d【1】=a【1】-a【0】=a【1】;
d【2】=a【2】-a【1】;
d【3】=a【3】-a【2】;
d【4】=a【4】-a【3】;
………以此类推

我们对d【i】求和,可以得到
Σd【i】=d【1】+d【2】+d【3】+d【4】+…….+d【n】=a【1】+a【2】-a【1】+a【3】-a【2】+…..+a【n】-a【n-1】
上方的式子做化简后得到求和
即,d【i】的前缀和结果就是a【i】

接下来我们如何表示a【i】的前缀和呢?
即sum(i)=a【1】+a【2】+a【3】+….+a【i】
代入上面的d【i】即:
a【1】=Σd【1】
a【2】=Σd【2】
a【3】=Σd【3】
…….
因此sum(i)=Σd【1】+Σd【2】+Σd【3】+….+Σd【i】
上方的每个数的求和都一串算式,那么对于这每一个算式,我们将其补全
何谓补全,即,Σd【1】=d【1】
Σd【2】=d【1】+d【2】
Σd【3】=d【1】+d【2】+d【3】
依次类推到
Σd【n】=d【1】+d【2】+d【3】+d【4】+…..+d【n】

我们称d【n】为完全的式子,那么补全的意思就是,除了Σd【n】之外的式子都是不完全的,我们要补上后面的加数使其变为Σd【n】,补全后,原先Σd【1】+Σd【2】+Σd【3】+….+Σd【i】就变成了n*Σd【n】
为了使等式左右两边相等,sum(i)≠n*Σd【n】
我们应该在补全之后,减去多余的,也就是我们补全时多加上的每个数。(对,就是这么麻烦的折腾,其实还是要维护的是求前缀和sum(i))

对于sum(i)=Σd【1】+Σd【2】+Σd【3】+….+Σd【i】中,我们发现,这个式子里有n个d【1】,那么补全后的n*Σd【n】里也存在n个d【1】,这是不需要删除的,因为没有多余。而d【2】,只有n-1个,Σd【1】中是没有的,因此我们应该删去1 * d【2】。
接下来d【3】,有n-2个,我们应该删去Σd【1】中多加的d【3】和Σd【2】中多加的d【3】,也就是删去了2*d【3】。

以此类推,我们补全的操作还多加了3*d【4】,4 * d【5】,5 * d【6】……(n-1) * d【n】

因此,最终 sum(i)被我们整理成
sum(i)=n * Σd【n】- (0*d【1】+1 * d【2】+2 * d【3】+3 * d【4】+ ….(n-1) * d【n】)

可以发现 ,上方的式子其实是两个前缀和的差,也就是说,我们要用两个树状数组来维护sum(i)的值。一个树状数组计算d【i】的前缀和,查询到的前缀和 ×i 即i*Σd【i】

另一个树状数组维护后半段式子的前缀和,也就是Σ (i-1)*d【i】。

到此,我们查询树状数组中一个前缀和的方法变为 sum(i)= i*query(tree1,i) - query(tree2,i)

这样大费周章的查询前缀和的目的是什么呢?我们将利用差值的特性将区间更新转变为单点更新。

举个例子,有四个元素:
a【1】 ,a【2】,a【3】, a【4】
我们对其进行区间【1,3】修改,同时加上一个值x,那么对于这个区间内数的d值是如何变化的
d【1】+x , d【2】, d【3】 , d【4】-x
因为这段区间内的值都增长了x,那么a【1】相对前一个元素差值多了x,因此d【1】+x,而对于区间内的值,a【2】与a【1】的差值是没有变化的,因此d【2】不变,后续的d【i】值也是如此。

而对于该区间末尾,a【4】与a【3】的差值,因为a【3】增长了x,那么a【4】-a【3】也就减少了x,即d【4】-x ,这是一次区间更新对区间两边界的影响,注意此处我们更新【1,3】,而修改的值是L和R+1的位置。因为【3】是被改变的,这个差值影响在d【4】才有所体现。

这样的更新转化到树状数组上,在tree1中,因为d【i】改变了,我们直接更新位置L和位置R+1的值。即update(tree1,L,add),区间的末尾update(tree1,R+1,-add)

在tree2中,我们记录的是(i-1) * d【i】的前缀和,其中,我们知道d【i】改变了,也就是说被更改后的值是(i-1) * (d【i】+x)
拆开后得(i-1) * d【i】+(i-1) * x
很明显,式子中加号前的值是原本就记录在tree2中的,而加号后面的值即我们要再树状数组中更新的增量。

此处我们更新的位置仍是数组中的L和R+1位,注意更新的值是(i-1) * x,此处的i表示更新位置,既然更新位置是R+1,那么此处即R * x,即代码中的update(tree2,L,(L-1) * add)update(tree2,R+1,R * -add)

在查询操作中,我们要查询的区间是【L,R】,那么就应该用前缀和sum(R) - sum(L-1) 作差得到结果。

至此树状数组的区间更新和区间查询操作步骤全部完成。
我们利用了数值间的差值关系来通过单点更新操作替代了区间更新操作,而使前缀和能记录下某个区间内的增值。

注意现在求一个单点的前缀和需要用两棵树的前缀和作差得到,而一个区间的加和要用两个前缀和之差再作差得到,这和单点更新时求区间和的原理是一样的。

代码如下:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n;
LL tree1[100008],tree2[100008];
int lowbit(int x)
{
    return x&(-x);
}
void update(LL tree[],int x,LL val)
{
    while(x<=n)
    {
        tree[x]+=val;
        x+=lowbit(x);
    }
}
LL query(LL tree[],int x)
{
    LL ans=0;
    while(x>0)
    {
        ans+=tree[x];
        x-=lowbit(x);
    }
    return ans;
}
LL a[100008];
int main()
{
    int t;
    while(scanf("%d%d",&n,&t)!=EOF)
    {
        a[0]=0;
        memset(tree1,0,sizeof tree1);
        memset(tree2,0,sizeof tree2);
        for(LL i=1;i<=n;i++)
        {
            LL tmp;
            scanf("%lld",&a[i]);
            update(tree1,i,a[i]-a[i-1]);
            update(tree2,i,(a[i]-a[i-1])*(i-1));
        }
        char com[3];
        while(t--)
        {
            scanf("%s",com);
            if(com[0]=='C')
            {
                LL l,r,add;
                scanf("%lld%lld%lld",&l,&r,&add);
                update(tree1,l,add);
                update(tree1,r+1,-add);
                update(tree2,l,add*(l-1));///(i-1)*(d[i]+x)=(i-1)*d[i]+(i-1)*x,  (i-1)为系数,在tree2中存储了(i-1)*d[i]的前缀和,而更新需要改变的值是(i-1)*x
                update(tree2,r+1,-add*r);///更新的位置是r+1和l本身
            }
            else
            {
                LL l,r;
                scanf("%lld%lld",&l,&r);
                LL ansl=(l-1)*query(tree1,l-1)-query(tree2,l-1);
                LL ansr=r*query(tree1,r)-query(tree2,r);
                printf("%lld
",ansr-ansl);
            }
        }
    }
}
原文地址:https://www.cnblogs.com/kuronekonano/p/11135784.html