当前位置: 首页 > news >正文

二维树状数组

二维树状数组

二维树状数组是用于维护 二维表信息 的一种数据结构,这样的信息也需要具有 结合律可差分性

比如要实现二维表的 单点修改子矩阵和查询

单点修改与子矩阵查询

子矩阵查询

对于子矩阵和查询,可以参照 二维前缀和 中的子矩阵和查询。

规定 sum[i][j]sum[i][j]sum[i][j] 表示以 (1,1)(1,1)(1,1) 为左上角,以 (i,j)(i,j)(i,j) 为右下角的子矩阵和,称 (i,j)(i,j)(i,j)前缀和,之后不再赘述。

那么求以 (x1,y1)(x_1,y_1)(x1,y1) 为左上角,以 (x2,y2)(x_2,y_2)(x2,y2) 为右下角的子矩阵和则可以写成若干前缀和的组合:
sum[x2][y2]−sum[x2][y1−1]−sum[x1−1][y2]+sum[x1−1][y1−1] sum[x_2][y_2]-sum[x_2][y_1-1]-sum[x_1-1][y_2]+sum[x_1-1][y_1-1] sum[x2][y2]sum[x2][y11]sum[x11][y2]+sum[x11][y11]
所以维护子矩阵和就等价于维护 矩阵的前缀和

而要查询 (i,j)(i,j)(i,j) 的前缀和,可以仿照一维树状数组,适当地对每个位置进行 信息压缩

对于 iii,设其 二进制拆分2k1+2k2+⋯+2km(k1<k2<⋯<km)2^{k_1}+2^{k_2}+\cdots+2^{k_m}(k_1<k_2<\cdots<k_m)2k1+2k2++2km(k1<k2<<km)

所以 1∼i1\sim i1i 行的信息可以 被压缩mmm 块信息。

tree[i]tree[i]tree[i] 就维护一个大小为 2k12^{k_1}2k1连续行信息,此时剩下 m−1m-1m1 块。

不妨设 j=i−2k1j=i-2^{k_1}j=i2k1,则 jjj 的二进制拆分为 2k2+⋯+2km(k2<⋯<km)2^{k_2}+\cdots+2^{k_m}(k_2<\cdots<k_m)2k2++2km(k2<<km)

所以 tree[j]tree[j]tree[j] 就维护一个大小为 2k22^{k_2}2k2 的连续行信息,此时剩下 m−2m-2m2 块。

以此类推…

此时得到了第一个 状态设计,就是 tree[i]tree[i]tree[i] 维护以 iii 行结尾的大小为 lowbit(i)lowbit(i)lowbit(i) 的连续行信息。

这样的话如果要查询 (i,j)(i,j)(i,j) 的前缀和,那么行只需要遍历 log(i)log(i)log(i) 次。

此时列仍没被压缩,所以每一列仍然需要 mmm 次遍历。

所以考虑 对列进行二进制拆分,过程与行类似。

此时得到了最终的状态设计tree[i][j]tree[i][j]tree[i][j] 维护以 iii 结尾的连续 lowbit(i)lowbit(i)lowbit(i) 行且以 jjj 结尾的连续 lowbit(j)lowbit(j)lowbit(j) 列的子矩阵信息。

故要查询 (i,j)(i,j)(i,j) 的前缀和,行只需要遍历 log(i)log(i)log(i) 次,列只需要遍历 log(j)log(j)log(j) 次。

故一次查询的时间复杂度是 O(log2n)O(log^2n)O(log2n)

单点修改

如果要修改 (x,y)(x, y)(x,y) 的值,那么就需要修改 所有能管辖到 (x,y)(x,y)(x,y) 的位置

对于行而言,能管辖到第 xxx 行的 最小位置xxx,同理,能管辖到第 yyy 列的 最小位置yyy

所以能管辖到 (x,y)(x,y)(x,y)最小位置tree[x][y]tree[x][y]tree[x][y]

在二维树状数组中,对于 任意一个 能管辖到 (x,y)(x,y)(x,y) 的位置 (x+t,y+z)(x+t,y+z)(x+t,y+z),我们如何能找到这个位置?

这个问题可以转换为两个一维树状数组上的问题。

对于行而言,如果 x+tx+tx+t 能管辖到 xxx,是否 x+tx+tx+t 一定能被 xxx 不断跳 lowbitlowbitlowbit 到达?

这个问题是肯定的,证明可以见 树状数组的性质 这一篇。

所以我们只需要从 (x,y)(x,y)(x,y) 开始,不断跳 lowbitlowbitlowbit 就能到达所有的管辖 (x,y)(x,y)(x,y) 的位置。

template<typename T>
struct Fenwick2D {int n, m;vector <vector<T>> tree;Fenwick2D(int _n, int _m) {n = _n;m = _m;tree.assign(n + 1, vector<T>(m + 1));}int lowbit (int x) {return x & (-x);}// 单点加 vvoid add(int x, int y, T v) {for (int i = x; i <= n; i += lowbit(i)) {for (int j = y; j <= m; j += lowbit(j)) {tree[i][j] += v;}}}// 求(x, y)前缀和T get_pre_sum(int x, int y) {T ans = 0;for (int i = x; i; i -= lowbit(i)) {for (int j = y; j; j -= lowbit(j)) {ans += tree[i][j];}}return ans;}// 求子矩阵和T get_range_sum(int x1, int y1, int x2, int y2) {return get_pre_sum(x2, y2) - get_pre_sum(x1 - 1, y2)- get_pre_sum(x2, y1 - 1) + get_pre_sum(x1 - 1, y1 - 1);}
};

子矩阵加与子矩阵查询

子矩阵加

现在要给以 (x1,y1)(x_1,y_1)(x1,y1) 为左上角,(x2,y2)(x_2,y_2)(x2,y2) 为右下角的子矩阵都加上 vvv,依旧可以往差分方向去考虑。

二维差分数组 d[i][j]d[i][j]d[i][j] 的定义是什么呢?

不妨参考一维差分数组,我们都知道差分的目的是 高效地 为了给一段区间加上相同的数。

且差分的前 iii 项和恰好等于原序列的第 iii 项,故有
a[i]=∑j=1id[j] a[i]=\sum_{j=1}^{i}d[j] a[i]=j=1id[j]
又因为
a[i]=∑j=1id[j]=∑j=1i−1d[j]+d[i]=a[i−1]+d[i] \begin{aligned} a[i]&=\sum_{j=1}^{i}d[j] =\sum_{j=1}^{i-1}d[j]+d[i]=a[i-1]+d[i] \end{aligned} a[i]=j=1id[j]=j=1i1d[j]+d[i]=a[i1]+d[i]
所以有
d[i]=a[i]−a[i−1] d[i]=a[i]-a[i-1] d[i]=a[i]a[i1]
等价替换,我们希望二维差分数组的前缀和满足
a[i][j]=∑k=1i∑t=1jd[k][t] a[i][j]=\sum_{k=1}^{i}\sum_{t=1}^{j}d[k][t] a[i][j]=k=1it=1jd[k][t]
而式子可以变形为
a[i][j]=∑k=1i∑t=1jd[k][t]=∑k=1i−1∑t=1jd[k][t]+∑k=1i∑t=1j−1d[k][t]−∑k=1i−1∑t=1j−1d[k][t]+d[i][j]=a[i−1][j]+a[i][j−1]−a[i−1][j−1]+d[i][j] \begin{aligned} a[i][j]&=\sum_{k=1}^{i}\sum_{t=1}^{j}d[k][t]\\ &=\sum_{k=1}^{i-1}\sum_{t=1}^{j}d[k][t]+\sum_{k=1}^{i}\sum_{t=1}^{j-1}d[k][t]-\sum_{k=1}^{i-1}\sum_{t=1}^{j-1}d[k][t]+d[i][j]\\ &=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+d[i][j]\\ \end{aligned} a[i][j]=k=1it=1jd[k][t]=k=1i1t=1jd[k][t]+k=1it=1j1d[k][t]k=1i1t=1j1d[k][t]+d[i][j]=a[i1][j]+a[i][j1]a[i1][j1]+d[i][j]
所以有
d[i][j]=a[i][j]−a[i−1][j]−a[i][j−1]+a[i−1][j−1] d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1] d[i][j]=a[i][j]a[i1][j]a[i][j1]+a[i1][j1]
此时我们得到了 二维差分数组的定义

接下来思考,应该怎么样对差分数组操作才能实现给子矩阵加上同一个数?

若我们给 d[x1][y1]d[x_1][y_1]d[x1][y1] 增加 vvvd[x1][y1]d[x_1][y_1]d[x1][y1] 的增加会影响所有满足 i≥x1,j≥y1i\ge x_1,j\ge y_1ix1,jy1 的位置 a[i][j]a[i][j]a[i][j]

而我们希望这个增加的值影响只限定在 x1≤i≤x2,y1≤j≤y2x_1\le i\le x_2,y_1\le j\le y_2x1ix2,y1jy2 范围内。

所以我们需要在 d[x2+1][y1]d[x_2+1][y_1]d[x2+1][y1] 处加上 −v-vv,这能阻止 (x2+1,y1)(x_2+1,y_1)(x2+1,y1) 及其右下方被影响。

我们还需要在 d[x1][y2+1]d[x_1][y_2+1]d[x1][y2+1] 处加上 −v-vv,这能阻止 (x1,y2+1)(x_1,y_2+1)(x1,y2+1) 及其右下方被影响。

但是可以发现 (x2+1,y2+1)(x_2+1,y_2+1)(x2+1,y2+1) 及其右下方被阻止了两次,所以还需要抵消一次阻止。

故在 d[x2+1][y2+1]d[x_2+1][y_2+1]d[x2+1][y2+1] 处加上 vvv

所以,子矩阵的修改变成了四次单点修改。
d[x1][y1]+v,d[x2+1][y1]−v,d[x1][y2+1]−v,d[x2+1][y2+1]+v \begin{aligned} d[x_1][y_1]+v,\quad d[x_2+1][y_1]-v,\quad d[x_1][y_2+1]-v,\quad d[x_2+1][y_2+1]+v \end{aligned} d[x1][y1]+v,d[x2+1][y1]v,d[x1][y2+1]v,d[x2+1][y2+1]+v

子矩阵和

由于要进行子矩阵加,所以我们维护的是原数组的二维差分数组。子矩阵和可以转换为若干的前缀和的组合。

(x,y)(x,y)(x,y) 的前缀和表示为
sum[x][y]=∑i=1x∑j=1ya[i][j]=∑i=1x∑j=1y(∑k=1i∑t=1jd[k][t]) \begin{aligned} sum[x][y]&=\sum_{i=1}^{x}\sum_{j=1}^{y}a[i][j]\\ &=\sum_{i=1}^{x}\sum_{j=1}^{y}(\sum_{k=1}^{i}\sum_{t=1}^{j}d[k][t])\\ \end{aligned} sum[x][y]=i=1xj=1ya[i][j]=i=1xj=1y(k=1it=1jd[k][t])
考虑化简(记忆化简的式子可以用打表的方式):

对于 a[x][y]a[x][y]a[x][y] 来说,它的表达式里 d[1][1]∼d[x][y]d[1][1]\sim d[x][y]d[1][1]d[x][y] 每个数都出现了一次。

对于 a[x][y−1]a[x][y-1]a[x][y1] 来说,它的表达式里 d[1][1]∼d[x][y−1]d[1][1]\sim d[x][y-1]d[1][1]d[x][y1] 每个数都出现了一次。

sum[x][y]sum[x][y]sum[x][y] 的表达式里 a[x][y]a[x][y]a[x][y]a[x][y−1]a[x][y-1]a[x][y1] 只出现了一次。

所以在 sum[x][y]sum[x][y]sum[x][y] 的表达式里中,d[x][y]d[x][y]d[x][y] 出现了一次,d[x][y−1]d[x][y-1]d[x][y1] 出现了两次。

以此类推 d[x][j]d[x][j]d[x][j] 出现了 y−j+1y-j+1yj+1 次,同理 d[i][y]d[i][y]d[i][y] 出现了 x−i+1x-i+1xi+1 次。

d[i][j]d[i][j]d[i][j] 出现了 (x−i+1)×(y−j+1)(x-i+1)\times(y-j+1)(xi+1)×(yj+1) 次。

则有
sum[x][y]=∑i=1x∑j=1yd[i][j]×(x−i+1)×(y−j+1)=∑i=1x∑j=1y(d[i][j]×(xy−yi+y−xj+ij−j+x−i+1))=∑i=1x∑j=1y(d[i][j]×(xy−(y+1)i−(x+1)j+ij+x+y+1))=(xy+x+y+1)∑i=1x∑j=1yd[i][j]−(y+1)∑i=1x∑j=1yi×d[i][j]−(x+1)∑i=1x∑j=1yj×d[i][j]+∑i=1x∑j=1yij×d[i][j] \begin{aligned} sum[x][y]&=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]\times(x-i+1)\times(y-j+1)\\ &=\sum_{i=1}^{x}\sum_{j=1}^{y}(d[i][j]\times(xy-yi+y-xj+ij-j+x-i+1))\\ &=\sum_{i=1}^{x}\sum_{j=1}^{y}(d[i][j]\times(xy-(y+1)i-(x+1)j+ij+x+y+1))\\ &=(xy+x+y+1)\sum_{i=1}^{x}\sum_{j=1}^{y} d[i][j]-(y+1)\sum_{i=1}^{x}\sum_{j=1}^{y}i\times d[i][j]-(x+1)\sum_{i=1}^{x}\sum_{j=1}^{y}j\times d[i][j]\\ &+\sum_{i=1}^{x}\sum_{j=1}^{y}ij\times d[i][j] \end{aligned} sum[x][y]=i=1xj=1yd[i][j]×(xi+1)×(yj+1)=i=1xj=1y(d[i][j]×(xyyi+yxj+ijj+xi+1))=i=1xj=1y(d[i][j]×(xy(y+1)i(x+1)j+ij+x+y+1))=(xy+x+y+1)i=1xj=1yd[i][j](y+1)i=1xj=1yi×d[i][j](x+1)i=1xj=1yj×d[i][j]+i=1xj=1yij×d[i][j]
所以我们需要维护 444 个树状数组来维护这个式子,分别是 treetree_itree_jtree_ij

#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18template<typename T>
struct Fenwick2D {int n, m;vector <vector<T>> tree, tree_i, tree_j, tree_ij;Fenwick2D(int _n, int _m) {n = _n;m = _m;tree.assign(n + 2, vector<T>(m + 2));tree_i.assign(n + 2, vector<T>(m + 2));tree_j.assign(n + 2, vector<T>(m + 2));tree_ij.assign(n + 2, vector<T>(m + 2));}int lowbit (int x) {return x & (-x);}void add(int x, int y, T v) {for (int i = x; i <= n; i += lowbit(i)) {for (int j = y; j <= m; j += lowbit(j)) {tree[i][j] += v;tree_i[i][j] += i*v;tree_j[i][j] += j*v;tree_ij[i][j] += i*j*v;}}}void add_range(int x1, int y1, int x2, int y2, T v) {add(x1, y1, v);add(x2 + 1, y1, - v);add(x1, y2 + 1, -v);add(x2 + 1, y2 + 1, v);}T get_pre_sum(int x, int y) {T ans = 0, ans_i = 0, ans_j = 0, ans_ij = 0;for (int i = x; i; i -= lowbit(i)) {for (int j = y; j; j -= lowbit(j)) {ans += tree[i][j];ans_i += tree_i[i][j];ans_j += tree_j[i][j];ans_ij += tree_ij[i][j];}}ans *= (x * y + x + y + 1);ans_i *= (y + 1);ans_j *= (x + 1);return ans - ans_i - ans_j + ans_ij;}T get_range_sum(int x1, int y1, int x2, int y2) {return get_pre_sum(x2, y2) - get_pre_sum(x1 - 1, y2)- get_pre_sum(x2, y1 - 1) + get_pre_sum(x1 - 1, y1 - 1);}};
void slove () {Fenwick2D<int> tree(5, 6);
}signed main () {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);slove();
}
http://www.lryc.cn/news/611311.html

相关文章:

  • 机器学习之线性回归与逻辑回归
  • 广州客户 戴尔R720服务器 liunx系统 RAID5无损升级扩容
  • 【递归完全搜索】USACO Bronze 2023 January - 牛栏降温 IIAir Cownditioning II
  • WordPress如何实现隐藏文章部分内容?WordPress无法解析[hide]...[/hide]这类短代码怎么办?
  • 深度清理C盘!adsC盘清理大师实现原理与技术解析
  • 2025《艾诺提亚失落之歌》逆向工程解包尝试
  • 一个小巧神奇的 USB数据线检测仪
  • SpringCloud学习-------Feign详解
  • PageHelper分页插件
  • makefile使用及双向链表
  • 在X86架构Linux中创建虚拟根目录并下载指定架构(如aarch64)的软件包(含依赖)
  • 数字图像处理(冈萨雷斯)第三版:第四章——频率域滤波(学前了解知识)——主要内容和重点
  • 深信服GO面试题及参考答案(下)
  • 数据结构基础:链表(2)——双向链表、循环链表、内核链表
  • GoLand 项目从 0 到 1:第五天 —— 角色权限中间件实现与事务控制
  • 前端工程化:Vue3(二)
  • 贝叶斯统计从理论到实践
  • 自动牙龈边缘识别软件设计与实现
  • Android AppSearch 深度解析:现代应用搜索架构与实践
  • 消息队列疑难问题(RocketMQ)
  • 认识爬虫 —— bs4提取
  • 阿里招AI产品运营
  • 永磁同步电机的矢量控制
  • RK3568下使用Qt 绘制实现实时坐标曲线
  • 【Spring Cloud】-- 注册中心
  • PowerShell 入门2: 使用帮助系统
  • 异或游戏 运算符优先级问题
  • GB28181监控平台LiveGBS如何配置GB28181对接海康、大华解码器上墙,将GB28181平台是视频给硬件解码器解码上墙
  • cJSON库应用
  • C语言的常见错误与调试