二维树状数组
二维树状数组
二维树状数组是用于维护 二维表信息 的一种数据结构,这样的信息也需要具有 结合律 和 可差分性。
比如要实现二维表的 单点修改 与 子矩阵和查询。
单点修改与子矩阵查询
子矩阵查询
对于子矩阵和查询,可以参照 二维前缀和 中的子矩阵和查询。
规定 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][y1−1]−sum[x1−1][y2]+sum[x1−1][y1−1]
所以维护子矩阵和就等价于维护 矩阵的前缀和。
而要查询 (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 i1∼i 行的信息可以 被压缩 为 mmm 块信息。
tree[i]tree[i]tree[i] 就维护一个大小为 2k12^{k_1}2k1 的 连续行信息,此时剩下 m−1m-1m−1 块。
不妨设 j=i−2k1j=i-2^{k_1}j=i−2k1,则 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-2m−2 块。
以此类推…
此时得到了第一个 状态设计,就是 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=1∑id[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=1∑id[j]=j=1∑i−1d[j]+d[i]=a[i−1]+d[i]
所以有
d[i]=a[i]−a[i−1]
d[i]=a[i]-a[i-1]
d[i]=a[i]−a[i−1]
等价替换,我们希望二维差分数组的前缀和满足
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=1∑it=1∑jd[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=1∑it=1∑jd[k][t]=k=1∑i−1t=1∑jd[k][t]+k=1∑it=1∑j−1d[k][t]−k=1∑i−1t=1∑j−1d[k][t]+d[i][j]=a[i−1][j]+a[i][j−1]−a[i−1][j−1]+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[i−1][j]−a[i][j−1]+a[i−1][j−1]
此时我们得到了 二维差分数组的定义。
接下来思考,应该怎么样对差分数组操作才能实现给子矩阵加上同一个数?
若我们给 d[x1][y1]d[x_1][y_1]d[x1][y1] 增加 vvv,d[x1][y1]d[x_1][y_1]d[x1][y1] 的增加会影响所有满足 i≥x1,j≥y1i\ge x_1,j\ge y_1i≥x1,j≥y1 的位置 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_2x1≤i≤x2,y1≤j≤y2 范围内。
所以我们需要在 d[x2+1][y1]d[x_2+1][y_1]d[x2+1][y1] 处加上 −v-v−v,这能阻止 (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-v−v,这能阻止 (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=1∑xj=1∑ya[i][j]=i=1∑xj=1∑y(k=1∑it=1∑jd[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][y−1] 来说,它的表达式里 d[1][1]∼d[x][y−1]d[1][1]\sim d[x][y-1]d[1][1]∼d[x][y−1] 每个数都出现了一次。
而 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][y−1] 只出现了一次。
所以在 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][y−1] 出现了两次。
以此类推 d[x][j]d[x][j]d[x][j] 出现了 y−j+1y-j+1y−j+1 次,同理 d[i][y]d[i][y]d[i][y] 出现了 x−i+1x-i+1x−i+1 次。
故 d[i][j]d[i][j]d[i][j] 出现了 (x−i+1)×(y−j+1)(x-i+1)\times(y-j+1)(x−i+1)×(y−j+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=1∑xj=1∑yd[i][j]×(x−i+1)×(y−j+1)=i=1∑xj=1∑y(d[i][j]×(xy−yi+y−xj+ij−j+x−i+1))=i=1∑xj=1∑y(d[i][j]×(xy−(y+1)i−(x+1)j+ij+x+y+1))=(xy+x+y+1)i=1∑xj=1∑yd[i][j]−(y+1)i=1∑xj=1∑yi×d[i][j]−(x+1)i=1∑xj=1∑yj×d[i][j]+i=1∑xj=1∑yij×d[i][j]
所以我们需要维护 444 个树状数组来维护这个式子,分别是 tree
,tree_i
,tree_j
,tree_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();
}