數據結構:二維樹狀數組、三維樹狀數組


二維樹狀數組涉及到兩種基本操作,修改矩陣中的一個點,查詢子矩陣的和

首先是修改點的操作:

void update(int x,int y,int z)
{
    for(int i=x;i<=n;i+=lowbit(i))
    for(int j=y;j<=m;j+=lowbit(j))
        c[i][j]+=z;
}

然后是查詢子矩陣的和,這里查詢的是從左上角到目標點所形成的矩陣的元素和

int sum(int x,int y)
{
    int ret=0;
    for(int i=x;i>=1;i-=lowbit(i))
    for(int j=y;j>=1;j-=lowbit(j))
        ret+=c[i][j];
    return ret;
}

那么如果我要查具體的一個子矩陣,就需要給出左上角的點和右下角的點的坐標,然后:

            int x1,y1,x2,y2;
            cin>>x1>>y1>>x2>>y2;
            cout<<sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1)<<endl;

就可以了

下面附上完整的二維樹狀數組的代碼:

 1 #include<iostream>
 2 using namespace std;
 3 const int maxn=1005;
 4 const int maxm=1005;
 5 int n,m;
 6 int q;
 7 int a[maxn][maxm];
 8 int c[maxn][maxm];
 9 int lowbit(int x)
10 {
11     return x&(-x);
12 }
13 void update(int x,int y,int z)
14 {
15     for(int i=x;i<=n;i+=lowbit(i))
16     for(int j=y;j<=m;j+=lowbit(j))
17         c[i][j]+=z;
18 }
19 
20 int sum(int x,int y)
21 {
22     int ret=0;
23     for(int i=x;i>=1;i-=lowbit(i))
24     for(int j=y;j>=1;j-=lowbit(j))
25         ret+=c[i][j];
26     return ret;
27 }
28 int main()
29 {
30     cin>>n>>m;
31     for(int i=1;i<=n;i++)
32     for(int j=1;j<=m;j++)
33     {
34         cin>>a[i][j];
35         update(i,j,a[i][j]);
36     }
37     cin>>q;
38     while(q--)
39     {
40         int x;
41         cin>>x;
42         if(x==1)
43         {
44             int y,z,w;
45             cin>>y>>z>>w;
46             update(y,z,w);
47         }
48         if(x==2)
49         {
50             int x1,y1,x2,y2;
51             cin>>x1>>y1>>x2>>y2;
52             cout<<sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1)<<endl;
53         }
54     }
55     return 0;
56 }

接下來我們對二維樹狀數組進行簡單的拓展,將其拓展為修改矩形區間,查詢點的二維樹狀數組

其實就是把二維差分的思想引入進去,當然,如果不用樹狀數組直接用二維差分數組也是完全可以的,這個時候修改區間變成了O(1),查詢點就變成了O(n),還是需要自己去權衡

二維樹狀數組的修改和查詢的函數還是完全不用去變的

修改區間就要這么修改了:

            update(x1,y1,w);
            update(x2+1,y2+1,w);
            update(x2+1,y1,-w);
            update(x1,y2+1,-w);

這個東西雖然是類比一維情況得來的,但是你不要去想,去在紙上畫一畫,主對角線端點為正,負對角線端點為負,然后就很顯然了

查詢單點的話直接sum(x,y)即可

這里給出完整的代碼:

 1 #include<iostream>
 2 using namespace std;
 3 const int maxn=1005;
 4 const int maxm=1005;
 5 int n,m;
 6 int q;
 7 int a[maxn][maxm];
 8 int c[maxn][maxm];
 9 int lowbit(int x)
10 {
11     return x&(-x);
12 }
13 void update(int x,int y,int z)
14 {
15     for(int i=x;i<=n;i+=lowbit(i))
16     for(int j=y;j<=m;j+=lowbit(j))
17         c[i][j]+=z;
18 }
19 
20 int sum(int x,int y)
21 {
22     int ret=0;
23     for(int i=x;i>=1;i-=lowbit(i))
24     for(int j=y;j>=1;j-=lowbit(j))
25         ret+=c[i][j];
26     return ret;
27 }
28 int main()
29 {
30     cin>>n>>m;
31     cin>>q;
32     while(q--)
33     {
34         int x;
35         cin>>x;
36         if(x==1)
37         {
38             int x1,y1,x2,y2,w;
39             cin>>x1>>y1>>x2>>y2>>w;
40             update(x1,y1,w);
41             update(x2+1,y2+1,w);
42             update(x2+1,y1,-w);
43             update(x1,y2+1,-w);
44         }
45         if(x==2)
46         {
47             int x,y;
48             cin>>x>>y;
49             cout<<sum(x,y)<<endl;
50         }
51     }
52     return 0;
53 }

接下來我們開始介紹運用二維樹狀數組來修改區間和查詢區間

這個問題的模板題是BZOJ3132,上帝造題的七分鍾,原題在網站上已經沒有了不知道怎么了

然后這個是直接把一維樹狀數組的修改區間和查詢區間的操作利用那個推出來的式子運用到了二維樹狀數組上面

保持二維樹狀數組的大體形式不做任何改變,只在修改和查詢上加東西:

            int x1,y1,x2,y2,w;
            cin>>x1>>y1>>x2>>y2>>w;
            update(c1,x1,y1,w),update(c1,x1,y2+1,-w);
            update(c1,x2+1,y1,-w),update(c1,x2+1,y2+1,w);
        
            update(c2,x1,y1,w*x1),update(c2,x2+1,y1,-w*(x2+1));
            update(c2,x1,y2+1,-w*x1),update(c2,x2+1,y2+1,w*(x2+1));
        
            update(c3,x1,y1,w*y1),update(c3,x2+1,y1,-w*y1);
            update(c3,x1,y2+1,-w*(y2+1)),update(c3,x2+1,y2+1,w*(y2+1));
        
            update(c4,x1,y1,w*x1*y1),update(c4,x2+1,y1,-w*(x2+1)*y1);
            update(c4,x1,y2+1,-w*x1*(y2+1)),update(c4,x2+1,y2+1,w*(x2+1)*(y2+1));

可以看到修改操作已經非常復雜了,然后是查詢,其實查詢更復雜,所以為了方便寫一個get函數:

int get(int x,int y)
{
    return sum(c1,x,y)*(x+1)*(y+1)-sum(c2,x,y)*(y+1)-(x+1)*sum(c3,x,y)+sum(c4,x,y);
}

然后就可以正常查詢了:

            int x1,y1,x2,y2;
            cin>>x1>>y1>>x2>>y2;
            cout<<get(x2,y2)-get(x2,y1-1)-get(x1-1,y2)+get(x1-1,y1-1)<<endl;

在這里是沒有讀入原始數據的,針對原始數據,我們有兩種方案,第一種方案是預處理出來一個前綴和,在查詢的時候把二維前綴和的結果也加進去

第二種方式是直接調用update函數把原始數據作為delta的一部分,其實對於復雜的情況來說,直接update就好了

下面給出運用二維樹狀數組修改區間和查詢區間的板子:

 1 #include<iostream>
 2 using namespace std;
 3 const int maxn=1005;
 4 const int maxm=1005;
 5 int n,m;
 6 int q;
 7 int a[maxn][maxm];
 8 int c1[maxn][maxm];
 9 int c2[maxn][maxm];
10 int c3[maxn][maxm];
11 int c4[maxn][maxm];
12 int lowbit(int x)
13 {
14     return x&(-x);
15 }
16 void update(int c[][maxm],int x,int y,int z)
17 {
18     for(int i=x;i<=n;i+=lowbit(i))
19     for(int j=y;j<=m;j+=lowbit(j))
20         c[i][j]+=z;
21 }
22 
23 int sum(int c[][maxm],int x,int y)
24 {
25     int ret=0;
26     for(int i=x;i>=1;i-=lowbit(i))
27     for(int j=y;j>=1;j-=lowbit(j))
28         ret+=c[i][j];
29     return ret;
30 }
31 
32 int get(int x,int y)
33 {
34     return sum(c1,x,y)*(x+1)*(y+1)-sum(c2,x,y)*(y+1)-(x+1)*sum(c3,x,y)+sum(c4,x,y);
35 }
36 int main()
37 {
38     cin>>n>>m;
39     cin>>q;
40     while(q--)
41     {
42         int x;
43         cin>>x;
44         if(x==1)
45         {
46             int x1,y1,x2,y2,w;
47             cin>>x1>>y1>>x2>>y2>>w;
48             update(c1,x1,y1,w),update(c1,x1,y2+1,-w);
49             update(c1,x2+1,y1,-w),update(c1,x2+1,y2+1,w);
50         
51             update(c2,x1,y1,w*x1),update(c2,x2+1,y1,-w*(x2+1));
52             update(c2,x1,y2+1,-w*x1),update(c2,x2+1,y2+1,w*(x2+1));
53         
54             update(c3,x1,y1,w*y1),update(c3,x2+1,y1,-w*y1);
55             update(c3,x1,y2+1,-w*(y2+1)),update(c3,x2+1,y2+1,w*(y2+1));
56         
57             update(c4,x1,y1,w*x1*y1),update(c4,x2+1,y1,-w*(x2+1)*y1);
58             update(c4,x1,y2+1,-w*x1*(y2+1)),update(c4,x2+1,y2+1,w*(x2+1)*(y2+1));
59         }
60         if(x==2)
61         {
62             int x1,y1,x2,y2;
63             cin>>x1>>y1>>x2>>y2;
64             cout<<get(x2,y2)-get(x2,y1-1)-get(x1-1,y2)+get(x1-1,y1-1)<<endl;
65         }
66     }
67     return 0;
68 }

在BZOJ3132中使用二維線段樹或者是樹套樹是過不了的,可以看到這種方法還是十分優越的

但是一定要注意數據范圍還有空間夠不夠,否則直接JJ

我們最后介紹三維樹狀數組:

怎么拓展呢?直接在二維樹狀數組的基礎上加一維就可以了,不用進行任何改動,這里我們只介紹其中的一種變式,那就是三維樹狀數組修改區間查詢點

(如果有人出三維樹狀數組修改區間查詢區間的那種題,直接在二維樹狀數組修改區間查詢區間的基礎上改,應該不會有這種題的)

下面給出代碼,着重觀察修改部分就可以了。

 1 #include<iostream>
 2 using namespace std;
 3 const int maxn=105;
 4 const int maxm=105;
 5 const int maxl=105;
 6 int n,m,l;
 7 int q;
 8 int a[maxn][maxm][maxl];
 9 int c[maxn][maxm][maxl];
10 int lowbit(int x)
11 {
12     return x&(-x);
13 }
14 void update(int x,int y,int z,int w)
15 {
16     for(int i=x;i<=n;i+=lowbit(i))
17     for(int j=y;j<=m;j+=lowbit(j))
18     for(int k=z;k<=l;k+=lowbit(k))
19         c[i][j][k]+=w;
20 }
21 
22 int sum(int x,int y,int z)
23 {
24     int ret=0;
25     for(int i=x;i>=1;i-=lowbit(i))
26     for(int j=y;j>=1;j-=lowbit(j))
27     for(int k=z;k>=1;k-=lowbit(k))
28         ret+=c[i][j][k];
29     return ret;
30 }
31 int main()
32 {
33     cin>>n>>m>>l;
34     cin>>q;
35     while(q--)
36     {
37         int x;
38         cin>>x;
39         if(x==1)
40         {
41             int x1,y1,z1,x2,y2,z2,w;
42             cin>>x1>>y1>>z1>>x2>>y2>>z2>>w;
43              update(x1,y1,z1,w); 
44              update(x1,y2+1,z1,-w); 
45              update(x2+1,y1,z1,-w); 
46              update(x2+1,y2+1,z1,w); 
47 
48              update(x1,y1,z2+1,-w); 
49              update(x1,y2+1,z2+1,w); 
50              update(x2+1,y1,z2+1,w); 
51              update(x2+1,y2+1,z2+1,-w); 
52         }
53         if(x==2)
54         {
55             int x,y,z;
56             cin>>x>>y>>z;
57             cout<<sum(x,y,z)<<endl;
58         }
59     }
60     return 0;
61 }

目測沒有三維修改查詢區間的變態題的


免責聲明!

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



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