樹狀數組進階 - 區間修改區間查詢、二維樹狀數組


目錄:

① 單點修改、區間查詢 樹狀數組

  原理

② 區間查詢、單點修改 樹狀數組

③ 區間查詢、區間修改 樹狀數組

④ 二維樹狀數組

  單點修改、區間查詢 二維樹狀數組

  區間修改、單點查詢 二維樹狀數組

  區間修改、區間查詢 二維樹狀數組

 


 

①單點修改、區間查詢BIT:

首先當然是最基礎的樹狀數組了,單點修改、區間查詢的樹狀數組代碼:

//BIT - 單點增加,區間查詢 - st
struct _BIT{
    int N,C[MAXN];
    int lowbit(int x){return x&(-x);}
    void init(int n) //初始化共有n個點
    {
        N=n;
        for(int i=1;i<=N;i++) C[i]=0;
    }
    void add(int pos,int val) //在pos點加上val
    {
        while(pos<=N)
        {
            C[pos]+=val;
            pos+=lowbit(pos);
        }
    }
    int ask(int pos) //查詢1~pos點的和
    {
        int ret=0;
        while(pos>0)
        {
            ret+=C[pos];
            pos-=lowbit(pos);
        }
        return ret;
    }
}BIT;
//BIT - 單點增加,區間查詢 - ed 

 

原理:

假設我們現在要維護的是a數組,我們實際存儲的是c數組,他們兩者的關系如圖:

    (稱a數組為原數組,而c數組是a數組的樹狀數組。)

有:

C1 = A1
C2 = A1+A2
C3 = A3
C4 = A1+A2+A3+A4
C5 = A5
C6 = A5+A6
C7 = A7
C8 = A1+A2+A3+A4+A5+A6+A7+A8

c[i]不再是簡單的存儲a[i],而是存儲了a[i]+a[i-1]+…+a[k],它存儲了從a[i]往前若干個元素的和,那么如何確定k呢?這就關系到lowbit函數……

 

lowbit(x)函數返回的是什么?先看下圖:

不難看出,lowbit(x)返回的是:若二進制下數字 $x$ 的尾部的零的個數為 k ,則lowbit(x) = ${2^k }$;

也就是說,c數組中,

  若i為奇數,$c\left[ i \right] = a\left[ i \right]$;

  若i為偶數,而其最多能整除k次2,$c\left[ i \right] = a\left[ i \right] + a\left[ {i - 1} \right] + \cdots + a\left[ {i - 2^k + 1} \right]$;

這樣一來,對於某個pos點的增加$x$,只要不斷令pos+=lowbit(pos),就相當於一直往父親節點走,所以我們在每個父親節點都要增加$x$。

 


 

②區間修改、單點查詢BIT:

其實這個的原理就是:通過差分把這個區間修改、單點查詢的問題轉化為①;

首先,假設我們要記錄的數組是$a\left[ {1:n} \right]$,那么我們假設有$d\left[ i \right] = a\left[ i \right] - a\left[ {i - 1} \right]$,且$d\left[ 1 \right] = a\left[ 1 \right]$,

顯然,就有$a\left[ i \right] = d\left[ 1 \right] + d\left[ 2 \right] + \cdots + d\left[ i \right]$,

我們在BIT中實際存儲的是數組$d\left[ {1:n} \right]$(准確的說是d數組的樹狀數組);

先說修改

  我們目標是給$a\left[ {L:R} \right]$全部加上$x$,那么我們不難發現,其實$d\left[ L+1 \right] , d\left[ L+2 \right] , \cdots , d\left[ R \right]$都沒有變化,

  而變化的只有:$d\left[ {L} \right]$增加了$x$,$d\left[ {R + 1} \right]$減少了$x$;

  所以只需要add(L,x),add(R+1,-x)即可。

再說查詢

  我們要單點查詢$a\left[ {pos} \right]$,由上可知$a\left[ {pos} \right] = d\left[ 1 \right] + d\left[ 2 \right] + \cdots + d\left[ {pos} \right]$,

  那么原來的sum(pos)函數不用修改,就正好能返回$a\left[ {pos} \right]$的值。

代碼:

//BIT - 區間修改,單點查詢 - st
struct _BIT{
    int N,C[MAXN];
    int lowbit(int x){return x&(-x);}
    void init(int n) //初始化共有n個點
    {
        N=n;
        for(int i=1;i<=N;i++) C[i]=0;
    }
    void add(int pos,int val)
    {
        while(pos<=N) C[pos]+=val,pos+=lowbit(pos);
    }
    void range_add(int l,int r,int x) //區間[l,r]加x
    {
        add(l,x);
        add(r+1,-x);
    }
    int ask(int pos) //查詢pos點的值
    {
        int ret=0;
        while(pos>0)
        {
            ret+=C[pos];
            pos-=lowbit(pos);
        }
        return ret;
    }
}BIT;
//BIT - 區間修改,單點查詢 - ed

 


 

③區間修改,區間查詢BIT:

這個就很騷氣了,這樣的BIT可以很輕松地應付線段樹模板題。

我們看到,由於我們目標記錄的是數組$a\left[ {1:n} \right]$,而實際存儲的是$d\left[ {1:n} \right]$,

那么已經實現了區間修改,如何完成區間查詢呢?顯然,區間查詢的基礎是快速求數組$a\left[ {1:n} \right]$的前綴和,

顯然數組$a\left[ {1:n} \right]$的前綴和:

  $a\left[ 1 \right] + a\left[ 2 \right] + \cdots + a\left[ i \right] = d\left[ 1 \right] \times i + d\left[ 2 \right] \times \left( {i - 1} \right) + \cdots + d\left[ i \right] \times 1$

不難發現右側可以化成:

  $\begin{array}{l}
d\left[ 1 \right] \times i + d\left[ 2 \right] \times \left( {i - 1} \right) + \cdots + d\left[ i \right] \times 1 \\
= \left[ {d\left[ 1 \right] \times \left( {i + 1} \right) + d\left[ 2 \right] \times \left( {i + 1} \right) + \cdots + d\left[ i \right] \times \left( {i + 1} \right)} \right] - \left[ {d\left[ 1 \right] \times 1 + d\left[ 2 \right] \times 2 + \cdots + d\left[ i \right] \times i} \right] \\
= \left( {i + 1} \right) \times \left( {d\left[ 1 \right] + d\left[ 2 \right] + \cdots + d\left[ i \right]} \right) - \left( {d\left[ 1 \right] \times 1 + d\left[ 2 \right] \times 2 + \cdots + d\left[ i \right] \times i} \right) \\
\end{array}$

這樣一來,我們就可以想到,在原來的數組$C\left[ i \right]$記錄$d\left[ i \right]$的基礎上,

再搞一個數組$C2\left[ i \right]$記錄$d\left[ i \right] \times i$即可。(當然,實際寫代碼的時候要明確,C數組和C2數組都是樹狀數組,不是原數組)

代碼:

//BIT - 區間修改,區間查詢 - st
struct _BIT{
    int N;
    ll C[MAXN],C2[MAXN]; //分別記錄d[i]和d[i]*i
    int lowbit(int x){return x&(-x);}
    void init(int n) //初始化共有n個點
    {
        N=n;
        memset(C,0,sizeof(C));
        memset(C2,0,sizeof(C2));
    }
    void add(int pos,ll val)
    {
        for(int i=pos;i<=N;i+=lowbit(i)) C[i]+=val,C2[i]+=val*pos;
    }
    void range_add(int l,int r,ll x) //區間[l,r]加上x
    {
        add(l,x);
        add(r+1,-x);
    }
    ll ask(int pos)
    {
        ll ret=0;
        for(int i=pos;i>0;i-=lowbit(i)) ret+=(pos+1)*C[i]-C2[i];
        return ret;
    }
    ll range_ask(int l,int r) //查詢區間[l,r]的和
    {
        return ask(r)-ask(l-1);
    }
}BIT;
//BIT - 區間修改,區間查詢 - ed

 


 

④二維樹狀數組:

  在一維樹狀數組中,數組C[x]記錄了的是右端點為x、長度為lowbit(x)的區間的區間和(具體參見原理)。

  那么我們也可以類似地定義C[x][y]記錄的是右下角為(x,y),高為 lowbit(x),寬為 lowbit(y) 的區間的區間和。

  這就是二維樹狀數組最基本的原理。

 

④-①單點修改、區間查詢 二維樹狀數組:

對於一維的樹狀數組稍加修改,就能得到:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1005;
const int maxm=1005;

struct BIT_2D
{
    int n,m;
    ll C[maxn][maxm];
    int lowbit(int x){return x&(-x);}
    void init(int n,int m) //初始化n行m列矩陣
    {
        this->n=n;
        this->m=m;
        memset(C,0,sizeof(C));
    }
    void add(int x,int y,ll val) //在點(x,y)加上val
    {
        for(int i=x;i<=n;i+=lowbit(i))
            for(int j=y;j<=m;j+=lowbit(j)) C[i][j]+=val;
    }
    ll ask(int x,int y) //求左上角為(1,1)右下角為(x,y)的矩陣和
    {
        ll ret=0;
        for(int i=x;i>0;i-=lowbit(i))
            for(int j=y;j>0;j-=lowbit(j)) ret+=C[i][j];
        return ret;
    }
}BIT;

int main()
{
    int n=10,m=10;
    BIT.init(n,m);

    BIT.add(1,1,2);
    BIT.add(3,6,7);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++) cout<<BIT.ask(i,j)<<"\t";
        cout<<endl;
    }
}

 

 

④-②區間修改、單點查詢 二維樹狀數組:

和之前的一維樹狀數組一樣,仿照②區間查詢、單點修改樹狀數組的操作,同樣用差分的方法,將本問題轉化為④-①,

由於數組$a\left[ i \right]\left[ j \right]$的前綴和有這樣的公式:

  $sum\left[ i \right]\left[ j \right] = \left( {sum\left[ {i - 1} \right]\left[ j \right] + sum\left[ i \right]\left[ {j - 1} \right] - sum\left[ {i - 1} \right]\left[ {j - 1} \right]} \right) + a\left[ i \right]\left[ j \right]$

套用求前綴和的形式,假設差分數組為$d\left[ i \right]\left[ j \right]$,可以有:

  $a\left[ i \right]\left[ j \right] = \left( {a\left[ {i - 1} \right]\left[ j \right] + a\left[ i \right]\left[ {j - 1} \right] - a\left[ {i - 1} \right]\left[ {j - 1} \right]} \right) + d\left[ i \right]\left[ j \right]$

就能得到:

  $d\left[ i \right]\left[ j \right] = a\left[ i \right]\left[ j \right] - \left( {a\left[ {i - 1} \right]\left[ j \right] + a\left[ i \right]\left[ {j - 1} \right] - a\left[ {i - 1} \right]\left[ {j - 1} \right]} \right)$

 

那么,同樣先說修改

  目標是給$\left( {x_1 ,y_1 } \right)$到$\left( {x_2 ,y_2 } \right)$的矩陣全部加上$x$,不難發現,差分數組變動的只有四個點:

  $d\left[ {x_1 } \right]\left[ {y_1 } \right] + = x\;,\;d\left[ {x_1 } \right]\left[ {y_2 + 1} \right] - = x\;,\;d\left[ {x_2 + 1} \right]\left[ {y_1 } \right] - = x\;,\;d\left[ {x_2 + 1} \right]\left[ {y_2 + 1} \right] + = x$

  這個可以自己舉個栗子驗證一下。

再說查詢

  同一維樹狀數組一樣,求左上角為$\left( {1,1} \right)$右下角為$\left( {x,y} \right)$的矩陣和就是d[x][y]的前綴和,正好就是要查詢的a[x][y]。

 

代碼:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1005;
const int maxm=1005;

struct BIT_2D
{
    int n,m;
    ll C[maxn][maxm];
    int lowbit(int x){return x&(-x);}
    void init(int n,int m) //初始化n行m列矩陣
    {
        this->n=n;
        this->m=m;
        memset(C,0,sizeof(C));
    }
    void add(int x,int y,ll val)
    {
        for(int i=x;i<=n;i+=lowbit(i))
            for(int j=y;j<=m;j+=lowbit(j)) C[i][j]+=val;
    }
    void range_add(int x1,int y1,int x2,int y2,ll x) //左上角為(x1,y1)右下角為(x2,y2)的矩陣全部加上x
    {
        add(x1,y1,x);
        add(x1,y2+1,-x);
        add(x2+1,y1,-x);
        add(x2+1,y2+1,x);
    }
    ll ask(int x,int y) //查詢點(x,y)的值
    {
        ll ret=0;
        for(int i=x;i>0;i-=lowbit(i))
            for(int j=y;j>0;j-=lowbit(j)) ret+=C[i][j];
        return ret;
    }
}BIT;

int main()
{
    int n=10,m=10;
    BIT.init(n,m);

    BIT.range_add(2,2,4,4,1);
    BIT.range_add(5,6,8,8,2);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++) cout<<BIT.ask(i,j)<<"\t";
        cout<<endl;
    }
}

 

④-②區間修改、區間查詢 二維樹狀數組:

又到了最亦可賽艇的部分了!依然仿照區間查詢、區間修改 樹狀數組的操作。

首先$sum\left[ i \right]\left[ j \right] = \sum\limits_{x = 1}^i {\sum\limits_{y = 1}^j {a\left[ x \right]\left[ y \right]} } $,

又由於$a\left[ x \right]\left[ y \right]{\rm{ = }}\sum\limits_{u = 1}^x {\sum\limits_{v = 1}^y {d\left[ u \right]\left[ v \right]} } $,

所以有:

  $sum\left[ i \right]\left[ j \right] = \sum\limits_{x = 1}^i {\sum\limits_{y = 1}^j {\left( {\sum\limits_{u = 1}^x {\sum\limits_{v = 1}^y {d\left[ u \right]\left[ v \right]} } } \right)} } $

可以說是非常復雜了……

但模仿③的操作,我們依然可以統計d[u][v]出現次數:

  不難想象,從$a\left[ 1 \right]\left[ 1 \right]$到$a\left[ i \right]\left[ j \right]$,$d\left[ 1 \right]\left[ 1 \right]$全部都要出現一次,所以有$i \times j$個$d\left[ 1 \right]\left[ 1 \right]$,即$d\left[ 1 \right]\left[ 1 \right] \times i \times j$

  類似的,有$d\left[ 1 \right]\left[ 2 \right] \times i \times \left( {j - 1} \right)\;,\;d\left[ 2 \right]\left[ 1 \right] \times \left( {i - 1} \right) \times j\;,\;d\left[ 2 \right]\left[ 2 \right] \times \left( {i - 1} \right) \times \left( {j - 1} \right)$ 等等等等……

所以我們不難把式子變成:

  $sum\left[ i \right]\left[ j \right] = \sum\limits_{x = 1}^i {\sum\limits_{y = 1}^j {\left[ {d\left[ x \right]\left[ y \right] \times \left( {i + 1 - x} \right) \times \left( {j + 1 - y} \right)} \right]} } $

展開得到:

  $sum\left[ i \right]\left[ j \right] = \sum\limits_{x = 1}^i {\sum\limits_{y = 1}^j {\left[ {d\left[ x \right]\left[ y \right] \cdot \left( {i + 1} \right)\left( {j + 1} \right) - d\left[ x \right]\left[ y \right] \cdot x\left( {j + 1} \right) - d\left[ x \right]\left[ y \right] \cdot \left( {i + 1} \right)y + d\left[ x \right]\left[ y \right] \cdot xy} \right]} } $

也就相當於把這個式子拆成了四個部分:

  $\begin{array}{l}
\left( {i + 1} \right)\left( {j + 1} \right) \times \sum\limits_{x = 1}^i {\sum\limits_{y = 1}^j {d\left[ x \right]\left[ y \right]} } \\
- \left( {j + 1} \right) \times \sum\limits_{x = 1}^i {\sum\limits_{y = 1}^j {\left( {d\left[ x \right]\left[ y \right] \cdot x} \right)} } \\
- \left( {i + 1} \right) \times \sum\limits_{x = 1}^i {\sum\limits_{y = 1}^j {\left( {d\left[ x \right]\left[ y \right] \cdot y} \right)} } \\
\sum\limits_{x = 1}^i {\sum\limits_{y = 1}^j {\left( {d\left[ x \right]\left[ y \right] \cdot xy} \right)} } \\
\end{array}$

所以我們需要在原來 $C1\left[ i \right]\left[ j \right]$ 記錄 ${d\left[ i \right]\left[ j \right]}$ 的基礎上,再添加三個樹狀數組:

  $C2\left[ i \right]\left[ j \right]$ 記錄 ${d\left[ i \right]\left[ j \right] \cdot i}$

  $C3\left[ i \right]\left[ j \right]$ 記錄 ${d\left[ i \right]\left[ j \right] \cdot j}$

  $C4\left[ i \right]\left[ j \right]$ 記錄 ${d\left[ i \right]\left[ j \right] \cdot ij}$

這樣一來,就能通過數組$a\left[ i \right]\left[ j \right]$的差分數組$d\left[ i \right]\left[ j \right]$來得到$a\left[ i \right]\left[ j \right]$的前綴和數組$sum\left[ i \right]\left[ j \right]$,

最后,易知$\left( {x_1 ,y_1 } \right)$到$\left( {x_2 ,y_2 } \right)$的矩陣和就等於$sum\left[ {x_2 } \right]\left[ {y_2 } \right] - sum\left[ {x_2 } \right]\left[ {y_1 - 1} \right] - sum\left[ {x_1 - 1} \right]\left[ {y_2 } \right] + sum\left[ {x_1 - 1} \right]\left[ {y_1 - 1} \right]$。

代碼:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1005;
const int maxm=1005;

struct BIT_2D
{
    int n,m;
    ll C1[maxn][maxm],C2[maxn][maxm],C3[maxn][maxm],C4[maxn][maxm];
    int lowbit(int x){return x&(-x);}
    void init(int n,int m) //初始化n行m列矩陣
    {
        this->n=n;
        this->m=m;
        memset(C1,0,sizeof(C1));
        memset(C2,0,sizeof(C2));
        memset(C3,0,sizeof(C3));
        memset(C4,0,sizeof(C4));
    }
    void add(int x,int y,ll val)
    {
        for(int i=x;i<=n;i+=lowbit(i))
        {
            for(int j=y;j<=m;j+=lowbit(j))
            {
                C1[i][j]+=val;
                C2[i][j]+=val*x;
                C3[i][j]+=val*y;
                C4[i][j]+=val*x*y;
            }
        }
    }
    void range_add(int x1,int y1,int x2,int y2,ll x) //左上角為(x1,y1)右下角為(x2,y2)的矩陣全部加上x
    {
        add(x1,y1,x);
        add(x1,y2+1,-x);
        add(x2+1,y1,-x);
        add(x2+1,y2+1,x);
    }
    ll ask(int x,int y) //查詢左上角為(1,1)右下角為(x,y)的矩陣和
    {
        ll ret=0;
        for(int i=x;i>0;i-=lowbit(i))
        {
            for(int j=y;j>0;j-=lowbit(j))
            {
                ret+=(x+1)*(y+1)*C1[i][j];
                ret-=(y+1)*C2[i][j]+(x+1)*C3[i][j];
                ret+=C4[i][j];
            }
        }
        return ret;
    }
    ll range_ask(int x1,int y1,int x2,int y2) //查詢左上角為(x1,y1)右下角為(x2,y2)的矩陣和
    {
        return ask(x2,y2)-ask(x1-1,y2)-ask(x2,y1-1)+ask(x1-1,y1-1);
    }
}BIT;

int main()
{
    int n=10,m=10;
    BIT.init(n,m);

    BIT.range_add(2,2,4,4,1);
    BIT.range_add(5,6,8,8,2);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++) cout<<BIT.range_ask(i,j,i,j)<<"\t";
        cout<<endl;
    }
    cout<<BIT.range_ask(2,2,4,4)<<endl;
    cout<<BIT.range_ask(5,6,8,8)<<endl;
    cout<<BIT.range_ask(2,2,8,8)<<endl;
}

 


本文主要參考https://www.cnblogs.com/RabbitHu/p/BIT.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM