【数据结构】二维树状数组

【数据结构】二维树状数组

一、二维树状数组

二维树状数组,其实就是一维的树状数组上的节点再套个树状数组,就变成了二维树状数组了。

const int N = 1e3 + 10;
int tr[N][N], n, m;

#define lowbit(x) (x & -x)

void add(int x, int y, int d) {
    for (int i = x; i <= n; i += lowbit(i))
        for (int j = y; j <= m; j += lowbit(j))
            tr[i][j] += d;
}
int query(int x, int y) {
    int ret = 0;
    for (int i = x; i; i -= lowbit(i))
        for (int j = y; j; j -= lowbit(j))
            ret += tr[i][j];
    return ret;
}

二、单点修改,区间查询

LOJ #133. 二维树状数组 1:单点修改,区间查询

给出一个 \(n × m\) 的零矩阵 \(A\) ,你需要完成如下操作:

  • \(1\)\(x\)\(y\)\(k\) :表示元素 \(A\)_{\(x\) , \(y\)} 自增 \(k\)
  • \(2\)\(a\)\(b\)\(c\)\(d\): 表示询问左上角为 \((a,b)\) ,右下角为 \((c,d)\) 的子矩阵内所有数的和

单点增加,因此可以直接加上就可以了

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 5000; // 2^(12)=4096

int n, m;

LL tr[N][N];
#define lowbit(x) (x & -x)
void add(int x, int y, int d) {
    for (int i = x; i <= n; i += lowbit(i))
        for (int j = y; j <= m; j += lowbit(j))
            tr[i][j] += d;
}
LL query(int x, int y) {
    LL ret = 0;
    for (int i = x; i; i -= lowbit(i))
        for (int j = y; j; j -= lowbit(j))
            ret += tr[i][j];
    return ret;
}

int main() {
    //加快读入
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    int opt;
    while (cin >> opt) {
        if (opt == 1) {
            int x, y, d;
            cin >> x >> y >> d;
            add(x, y, d);
        } else {
            int x1, y1, x2, y2;
            cin >> x1 >> y1 >> x2 >> y2;
            cout << query(x2, y2) - query(x1 - 1, y2) - query(x2, y1 - 1) + query(x1 - 1, y1 - 1) << '\n';
        }
    }
    return 0;
}

三、区间修改,单点查询

LOJ #134. 二维树状数组 2:区间修改,单点查询

给出一个 \(n × m\) 的零矩阵 \(A\) ,你需要完成如下操作:

  • \(1 \, a \, b \, c \, d \, k\):表示左上角为 \((a,b)\) ,右下角为 \((c,d)\) 的子矩阵内所有数都自增加 \(k\)
  • \(2 \, x \, y\) :表示询问元素 \(A_{x,y}\) 的值。

只需要利用一个二维树状数组,维护一个二维差分数组,单点查询即可。

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 5000;
int bit[N][N];
int n, m;

LL tr[N][N];
#define lowbit(x) (x & -x)
void add(int x, int y, int d) {
    for (int i = x; i <= n; i += lowbit(i))
        for (int j = y; j <= m; j += lowbit(j))
            tr[i][j] += d;
}
LL query(int x, int y) {
    LL ret = 0;
    for (int i = x; i; i -= lowbit(i))
        for (int j = y; j; j -= lowbit(j))
            ret += tr[i][j];
    return ret;
}

int main() {
    //加快读入
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> n >> m;
    int op;
    while (cin >> op) {
        if (op == 1) {
            int x1, y1, x2, y2, d;
            cin >> x1 >> y1 >> x2 >> y2 >> d;
            //二维差分
            add(x1, y1, d);
            add(x1, y2 + 1, -d);
            add(x2 + 1, y1, -d);
            add(x2 + 1, y2 + 1, d);
        } else {
            int x, y;
            cin >> x >> y;
            cout << query(x, y) << '\n';
        }
    }
    return 0;
}

四、区间修改,区间查询

LOJ #135. 二维树状数组 3:区间修改,区间查询

给定一个大小为 \(N × M\) 的零矩阵,直到输入文件结束,你需要进行若干个操作,操作有两类:

  • \(1 \, a\, b\, c\, d\, x\),表示将左上角为 \((a,b)\) ,右下角为 \((c,d)\) 的子矩阵全部加上 \(x\)

  • \(2\, a\, b\, c\, d\,\) , 表示询问左上角为 \((a,b)\) ,右下角为 \((c,d)\) 为顶点的子矩阵的所有数字之和。

考虑前缀和 \(sum[i][j]\) 和 原数组 \(a\) , 差分数组 \(d\) 之间的关系。

首先\(\displaystyle sum[i][j]=\sum_{x=1}^i\sum_{y=1}^ja[x][y]\) (二维前缀和)

双由于\(\displaystyle a[x][y]=\sum_{u=1}^x\sum_{v=1}^yd[u][v]\) (差分数组与原数组关系)

所以:

\[\displaystyle sum[i][j]=\sum_{x=1}^i\sum_{y=1}^j\sum_{u=1}^x\sum_{v=1}^yd[u][v] \]

可以说是非常复杂了......

统计\(d[u][v]\)出现次数

  • \(a[1][1]\)\(a[i][j],d[1][1]\)全都要出现一次,所以有\(i×j\)\(d[1][1]\),即\(d[1][1]×i×j\)

  • \(a[1][1]\)\(a[i][j]\),\(d[1][2]\)出现了多少次呢?头脑中出现一个二维差分转原数组(本质就是一个原数组转二维前缀和)的图像:

    • \(i=1,j=1\)时, \(d[1][2]\)就没有出现
    • \(i=1,j=2\)时, \(d[1][2]\)出现\(1\)
    • ...
    • \(i=2,j=1\)时, \(d[1][2]\)就没有出现
    • \(i=2,j=2\)时, \(d[1][2]\)出现\(1\)
    • ...

总结一下:

  • \(d[1][2]×i×(j−1)\)
  • \(d[2][1]×(i−1)×j\)
  • \(d[2][2]×(i−1)×(j−1)\)
    等等……

所以我们不难把式子变成:

\[sum[i][j]=\sum_{x=1}^i\sum_{y=1}^j \begin{bmatrix}d[x][y]×(i+1−x)×(j+1−y)\end{bmatrix} \]

展开得到:

\[sum[i][j]=\sum_{x=1}^i\sum_{y=1}^j \begin{bmatrix}d[x][y]\times (i+1)\times (j+1)-d[x][y]\times x \times (j+1)-d[x][y]\times (i+1) \times y+d[x][y]\times xy \end{bmatrix} \]

也就相当于把这个式子拆成了四个部分:
\(\displaystyle ① (i+1)(j+1)×\sum_{x=1}^i\sum_{y=1}^jd[x][y] \\ ② −(j+1)×\sum_{x=1}^i\sum_{y=1}^j(d[x][y]⋅x) \\ ③ −(i+1)×\sum_{x=1}^i\sum_{y=1}^j(d[x][y]⋅y) \\ ④ \sum_{x=1}^i\sum_{y=1}^j(d[x][y]⋅xy)\)

所以我们需要在原来 \(C_1[i][j]\) 记录 \(d[i][j]\) 的基础上,再添加三个树状数组:

\(C_2[i][j]\) 记录 \(d[i][j]⋅i\)
\(C_3[i][j]\) 记录 \(d[i][j]⋅j\)
\(C_4[i][j\)] 记录 \(d[i][j]⋅ij\)

这样一来,就能通过数组\(a[i][j]\)的差分数组\(d[i][j]\)来得到\(a[i][j]\)的前缀和数组\(sum[i][j]\)

最后,易知\((x_1,y_1)\)\((x_2,y_2)\)的矩阵和就是一个标准的二维前缀和公式,等于$$\large sum[x_2][y_2]−sum[x_2][y_1−1]−sum[x_1−1][y_2]+sum[x_1−1][y_1−1]$$

代码模板

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2050;

int n, m;
LL C1[N][N], C2[N][N], C3[N][N], C4[N][N];
#define lowbit(x) (x & -x)

//维护四个树状数组
void add(int x, int y, int d) {
    for (int i = x; i <= n; i += lowbit(i))
        for (int j = y; j <= m; j += lowbit(j)) {
            C1[i][j] += d;
            C2[i][j] += d * x;
            C3[i][j] += d * y;
            C4[i][j] += d * x * y;
        }
}

//查询左上角为(1,1)右下角为(x,y)的矩阵和
LL query(int x, int 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];
            ret -= (x + 1) * C3[i][j];
            ret += C4[i][j];
        }
    }
    return ret;
}

int main() {
    //加快读入
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    int op;
    while (cin >> op) {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        if (op == 1) {
            int d;
            cin >> d;
            //维护四个数组
            add(x1, y1, d);
            add(x1, y2 + 1, -d);
            add(x2 + 1, y1, -d);
            add(x2 + 1, y2 + 1, d);
        } else
            cout << query(x2, y2) - query(x1 - 1, y2) - query(x2, y1 - 1) + query(x1 - 1, y1 - 1) << '\n';
    }
    return 0;
}
posted @ 2022-12-09 14:29  糖豆爸爸  阅读(1025)  评论(0)    收藏  举报
Live2D