数据结构:二维树状数组、三维树状数组

二维树状数组涉及到两种基本操作,修改矩阵中的一个点,查询子矩阵的和

首先是修改点的操作:

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 }

目测没有三维修改查询区间的变态题的

原文地址:https://www.cnblogs.com/aininot260/p/9336527.html