天天看點

poj3468 A Simple Problem with Integers(樹狀數組區間修改,區間查詢詳解)

A Simple Problem with Integers

Time Limit: 5000MS Memory Limit: 131072K

Total Submissions: 172750 Accepted: 53177

Case Time Limit: 2000MS

Description

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

題目大意:

給你n個值,m個詢問,當詢問為Q表示[a,b]區間和,C a b c 表示區間[a,b]加上c

思路:

這個題已經用線段樹的lazy标簽做過一次了,現在給出樹狀數組做法。

假設大家都已經會樹狀數組的單點查詢,單點更新,區間查詢操作了。現在我們主要讨論的是區間修改,區間查詢。

首先引入一個概念:

差分數組 所謂差分數組就是相鄰兩個數的差組成的數組。

這麼這個數組有什麼好處呢?

首先假設存在這樣一組序列sum[i]= 1 2 3 4 5 然後求他們的差分數組為 a[i]= 1 1 1 1 1,然後你會發現一個不錯的性質 ∑ i = 1 x a [ i ] \displaystyle\sum_{i=1}^{x} a[i] i=1∑x​a[i]=sum[i] ,也就是說序列的第i個數的值就會等于差分數組前i個數之和。(有沒有想過問什麼?這個不難證明,就不多說了)

有了這個性質我們是不是可以用他求出各個數的值,但是他的時間複雜度是O(n),有人就會問了,不用差分數組直接存複雜度O(1),引入差分數組不是多此一舉嗎?其實不是這樣的,因為差分數組最終要做的是求字首和,那麼我們可以用數組數組來構造這個差分數組,這樣他的查詢,和修改的複雜度就全部優化到了O(log n)了,而你用數組直接存詢問複雜度O(1),修改O(n),對于多次修改,差分樹狀數組的有點就展現出來了。

有了差分樹狀這組這個概念,接下來我給出兩個很重要的公式。

我們是用差分樹狀數組求sum[i]的值,那麼 ∑ i = 1 x s u m [ i ] \displaystyle\sum_{i=1}^{x} sum[i] i=1∑x​sum[i],那麼這個要怎麼求呢?其實很簡單,因為有 ∑ i = 1 x a [ i ] \displaystyle\sum_{i=1}^{x} a[i] i=1∑x​a[i]=sum[i] ,是以 ∑ i = 1 x s u m [ i ] \displaystyle\sum_{i=1}^{x} sum[i] i=1∑x​sum[i]= ∑ i = 1 x \displaystyle\sum_{i=1}^{x} i=1∑x​ ∑ j = 1 i a [ i ] \displaystyle\sum_{j=1}^{i} a[i] j=1∑i​a[i] (這個公式應該也不難想到,自己在本子上畫一畫就知道了)

那麼這個公式那麼時間複雜度是(logn *log n),顯然我們可以繼續優化。

将公式展開 ∑ i = 1 x ( x + 1 − i ) a [ i ] \displaystyle\sum_{i=1}^{x} (x+1-i)a[i] i=1∑x​(x+1−i)a[i]=x * ∑ i = 1 x a [ i ] \displaystyle\sum_{i=1}^{x} a[i] i=1∑x​a[i] - ∑ i = 1 x a [ i ] ∗ ( i − 1 ) \displaystyle\sum_{i=1}^{x} a[i]*(i-1) i=1∑x​a[i]∗(i−1)(為什麼展開成這樣了?因為求字首和時候,第一個數出現x次,第二數出現,(x-1)次,第三個數以此類推,然後就成這樣了)

那麼如果我們用一個數組s1維護 ∑ i = 1 x a [ i ] \displaystyle\sum_{i=1}^{x} a[i] i=1∑x​a[i],另外一個數組s2維護 ∑ i = 1 x a [ i ] ∗ ( i − 1 ) \displaystyle\sum_{i=1}^{x} a[i]*(i-1) i=1∑x​a[i]∗(i−1),那麼時間複雜度就 優化到了O(logn),怎麼樣?是不是很神奇?

有了上面的知識我們怎麼做區間求和呢,假如給你區間[2,3]那麼他的和是不是[1,3]的和減去[1,1]的和,[1,3]的和怎麼求呢?

∑ i = 1 x s u m [ i ] \displaystyle\sum_{i=1}^{x} sum[i] i=1∑x​sum[i]=x* ∑ i = 1 x a [ i ] \displaystyle\sum_{i=1}^{x} a[i] i=1∑x​a[i] - ∑ i = 1 x a [ i ] ∗ ( i − 1 ) \displaystyle\sum_{i=1}^{x} a[i]*(i-1) i=1∑x​a[i]∗(i−1) 是不是就是這個公式?轉化成代碼就是x*query(x,s1)-query(x,x2),然後把3帶入公式中的x就可以求出[1,3]的區間和了。[1,1]也是同理。

那麼區間修改又要怎麼做呢?

假如現在要将區間[x,y]加上一個常數c,是不是隻需要修改s1,讓數組下标x加上c,然後樹狀數組一直傳遞到樹根,前面的不變,那麼x前面的數求和是不是不變,後面的數求和時全部加上了一個常數c,這顯然沒達到我們的要求,我們再将y+1數組下标減去這個常數c,也就是加上-c,這樣是不是就隻有[x,y]區間加上了常數c,注意差分樹狀數組求和是x-=(x&-x),這個大家應該都懂。另外一個數組s2也是同理,轉化成代碼add(l,v,s1);add(r+1,-v,s2);add(l,(l-1)*v,s1);add(r+1,(r+1-1) * (-v),s2);

如果還有什麼不懂,大家再慢慢去想吧,畢竟我想了三天才想通(斷斷續續,畢竟課太多,瘋狂為自己的弱找借口)

下面給個這個題的代碼:

#include<iostream>
#include<cstdio>
using namespace std;

const int maxn=1e6+10;
typedef long long ll;
ll N,M;
ll sum[maxn]={0},s1[maxn]={0},s2[maxn]={0};
void add(ll x,ll k,ll s[]){
    for(;x<=N;x+=(x&-x)){
        s[x]+=k;
    }
}
ll query(ll x,ll s[]){
    ll ans=0;
    for(;x;x-=(x&-x)){
        ans+=s[x];
    }
    return ans;
}
int main(){
    scanf("%lld%lld",&N,&M);
    for(ll i=1;i<=N;i++){
        scanf("%lld",&sum[i]);
        add(i,sum[i]-sum[i-1],s1);
        add(i,(i-1)*(sum[i]-sum[i-1]),s2);
    }
    while(M--){
        int ins;
        ll x,y,z;
        scanf("%d",&ins);
        if(ins==1){
            scanf("%lld%lld%lld",&x,&y,&z);
            add(x,z,s1);add(y+1,-z,s1);
            add(x,z*(x-1),s2);add(y+1,-z*(y),s2);
        }else if(ins==2){
            scanf("%lld%lld",&x,&y);
            ll ans1=y*query(y,s1)-query(y,s2);
            ll ans2=(x-1)*query(x-1,s1)-query(x-1,s2);
            printf("%lld\n",ans1-ans2);
        }
    }
}