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

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

一、二维树状数组

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

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 ,你需要完成如下操作:

  • 1xyk :表示元素 A_{x , y} 自增 k
  • 2abcd: 表示询问左上角为 (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 ,你需要完成如下操作:

  • 1abcdk:表示左上角为 (a,b) ,右下角为 (c,d) 的子矩阵内所有数都自增加 k
  • 2xy :表示询问元素 Ax,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 的零矩阵,直到输入文件结束,你需要进行若干个操作,操作有两类:

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

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

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

首先sum[i][j]=x=1iy=1ja[x][y] (二维前缀和)

双由于a[x][y]=u=1xv=1yd[u][v] (差分数组与原数组关系)

所以:

sum[i][j]=x=1iy=1ju=1xv=1yd[u][v]

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

统计d[u][v]出现次数

  • a[1][1]a[i][j],d[1][1]全都要出现一次,所以有i×jd[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×(j1)
  • d[2][1]×(i1)×j
  • d[2][2]×(i1)×(j1)
    等等……

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

sum[i][j]=x=1iy=1j[d[x][y]×(i+1x)×(j+1y)]

展开得到:

sum[i][j]=x=1iy=1j[d[x][y]×(i+1)×(j+1)d[x][y]×x×(j+1)d[x][y]×(i+1)×y+d[x][y]×xy]

也就相当于把这个式子拆成了四个部分:
(i+1)(j+1)×x=1iy=1jd[x][y](j+1)×x=1iy=1j(d[x][y]x)(i+1)×x=1iy=1j(d[x][y]y)x=1iy=1j(d[x][y]xy)

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

C2[i][j] 记录 d[i][j]i
C3[i][j] 记录 d[i][j]j
C4[i][j] 记录 d[i][j]ij

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

最后,易知(x1,y1)(x2,y2)的矩阵和就是一个标准的二维前缀和公式,等于sum[x2][y2]sum[x2][y11]sum[x11][y2]+sum[x11][y11]

代码模板

#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 @   糖豆爸爸  阅读(659)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2021-12-09 AcWing 1021. 货币系统
2021-12-09 AcWing 1023. 买书
2021-12-09 AcWing 278. 数字组合
2021-12-09 AcWing 1022. 宠物小精灵之收服
2021-12-09 AcWing 1024. 装箱问题
2021-12-09 AcWing 423. 采药
2017-12-09 为预热准备更新时间列的查询办法,解决原表中没有索引的问题
Live2D
点击右上角即可分享
微信分享提示