树状数组
什么是树状数组
树状数组是一个查询、修改的时间复杂度都为O(logn)的数据结构,它巧妙地利用了二分法的思想。
相较于普通数组,树状数组牺牲了单点查询和单点修改的时间将区间修改和区间查询的时间复杂度从O(n)降到了O(logn),这使得它在处理大量数据时占有较大优势。
使用树状数组时,我们并不直接将每个数据存储到对应的数组元素中,而是将一段区间和存储在相应的数组元素中。
如图,对于普通的数组a[n],a[i]的值即为i号位元素的值,而对于树状数组c[n],
c[1] = a[1]
c[2] = a[1] + a[2]
c[3] = a[3]
c[4] = a[1] + a[2] + a[3] + a[4]
c[5] = a[5]
c[6] = a[5] + a[6]
c[7] = a[7]
c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8]
可以发现,c[i]是数组a某一个区间内所有元素的和,并且这些区间的范围是有规律的。每个c[i]所管辖的区间内元素的个数为
2
k
2^k
2k,其中,k为i所对应的二进制数末尾0的个数。而且每个c[i]所管辖的区间内最后一个元素必定为a[i],因此,
c
[
i
]
=
∑
j
=
i
−
2
k
+
1
i
a
[
j
]
c[i]=\sum_{j=i-2^k+1}^ia[j]
c[i]=j=i−2k+1∑ia[j]
例如:
3对应的二进制为11,末尾没有0,即k为0,因此c[3]所管辖的区间内元素个数为
2
0
=
1
2^0=1
20=1
c
[
3
]
=
∑
j
=
3
−
2
0
+
1
3
a
[
j
]
=
∑
j
=
3
3
a
[
j
]
=
a
[
3
]
c[3]=\sum_{j=3-2^0+1}^3a[j]=\sum_{j=3}^3a[j]=a[3]
c[3]=j=3−20+1∑3a[j]=j=3∑3a[j]=a[3]
8对应的二进制为1000,末尾有三个0,即k为3,因此c[8]所管辖的区间内元素个数为
2
3
=
8
\ 2^3=8
23=8
c
[
8
]
=
∑
j
=
8
−
2
3
+
1
8
a
[
j
]
=
∑
j
=
1
8
a
[
j
]
=
a
[
1
]
+
a
[
2
]
+
a
[
3
]
+
a
[
4
]
+
a
[
5
]
+
a
[
6
]
+
a
[
7
]
+
a
[
8
]
c[8]=\sum_{j=8-2^3+1}^8a[j]=\sum_{j=1}^8a[j]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]
c[8]=j=8−23+1∑8a[j]=j=1∑8a[j]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]
lowbit()函数
确定 2 k \ 2^k 2k的值有一个十分简单的方法,即lowbit()函数
int lowbit(int x)
{
return x & -x;
}
例如:
当x = 3时,3对应的二进制为11,-3的补码为01,所以3 & -3的结果为01,即为1
当x = 6时,6对应的二进制为110,-6的补码为010,所以6 & -6的结果为010,即为2
当x = 8时,8对应的二进制为1000,-8的补码为1000,所以8 & -8的结果为1000,即为8
由于进行了与操作,符号位必定为0,所以lowbit()函数返回值必定为正数。
树状数组的实现
普通的数组已经可以实现单点修改与单点查询功能,而树状数组还可以实现
- 单点修改与区间查询
- 区间修改与单点查询
- 区间修改与区间查询
三个功能。
单点修改与区间查询
- 单点修改
为了实现快速的区间查询,我们并没有真正的存储a数组,而是间接地用树状数组c来存储a的值,而树状数组c每个元素的值是a数组的某段区间的区间和。因此,想要修改其中一点a[i]的值,就要修改所有包含a[i]的c数组中元素的值。
那么a[i]都包含在哪些c数组的元素中呢?
如图,a[3]包含在c[3]中,c[3]包含在c[4]中,c[4]包含在c[8]中,c[8]包含在c[16]中。因此,a[3]包含在c[3]、c[4]、c[8]、c[16]…中。
那么对于任意的a[i],应该如何确定?
令 2 k 1 = l o w b i t ( i ) 2^{k_1}=lowbit(i) 2k1=lowbit(i)
2 k 2 = l o w b i t ( i + 2 k 1 ) 2^{k_2}=lowbit(i+2^{k_1}) 2k2=lowbit(i+2k1)
2 k 3 = l o w b i t ( i + 2 k 2 ) 2^{k_3}=lowbit(i+2^{k_2}) 2k3=lowbit(i+2k2)
···
如图,箭头所指方向即为i+lowbit(i)的移动方向。i+lowbit(i)实际上就是将i移动到最近一层的包含c[i]的c[i + 2 k 2^k 2k]的末端点上。
观察一下就能得到,a[i]包含在c[i],c[i + 2 k 1 2^{k_1} 2k1],c[i + 2 k 1 2^{k_1} 2k1 + 2 k 2 2^{k_2} 2k2],c[i + 2 k 1 2^{k_1} 2k1 + 2 k 2 2^{k_2} 2k2 + 2 k 3 2^{k_3} 2k3]···中。
这样就可以写出单点修改的算法。
void node_add(int loc, long long val)
{
for (int i = loc; i <= size; i += lowbit(i)) c[i] += val;
}
可以看出,这个算法的时间复杂度是O(logn),普通数组的时间复杂度是O(1)。也就是说,我们牺牲了单点修改的时间。但通过树状数组我们可以缩短区间查询的时间。
2. 区间查询
由于树状数组是由二分法形成的,每段区间的范围都是
2
k
2^k
2k,对于任意区间[l, r],直接计算
∑
i
=
l
r
a
[
i
]
\sum_{i=l}^ra[i]
∑i=lra[i]是很困难的,而对于从1开始的[1,i]区间,是很容易计算的:我们可以将
∑
j
=
1
i
a
[
j
]
\sum_{j=1}^ia[j]
∑j=1ia[j],拆分成若干个c[k]的值相加。
令
2
k
1
=
l
o
w
b
i
t
(
i
)
2^{k_1}=lowbit(i)
2k1=lowbit(i)
2
k
2
=
l
o
w
b
i
t
(
i
−
2
k
1
)
2^{k_2}=lowbit(i-2^{k_1})
2k2=lowbit(i−2k1)
2
k
3
=
l
o
w
b
i
t
(
i
−
2
k
2
)
2^{k_3}=lowbit(i-2^{k_2})
2k3=lowbit(i−2k2)
···
如图,箭头所指方向为i-lowbit(i)的移动方向。i-lowbit(i)实际上就是将i移动到c[i]所管辖的区间的首端点之前。
观察一下就能得到,
∑
j
=
1
i
a
[
j
]
=
c
[
i
]
+
c
[
i
−
2
k
1
]
+
c
[
i
−
2
k
1
−
2
k
2
]
+
c
[
i
−
2
k
1
−
2
k
2
−
2
k
3
]
+
…
\sum_{j=1}^ia[j]=c[i]+c[i-2^{k_1}]+c[i-2^{k_1}-2^{k_2}]+c[i-2^{k_1}-2^{k_2}-2^{k_3}]+\dots
j=1∑ia[j]=c[i]+c[i−2k1]+c[i−2k1−2k2]+c[i−2k1−2k2−2k3]+…
代码为:
long long sum(int loc)
{
long long s = 0;
for (int i = loc; i >= 1; i -= lowbit(i)) s += c[i];
return s;
}
对于a数组的任意区间[l,r]的区间和有,
∑
i
=
l
r
a
[
i
]
=
∑
i
=
1
r
a
[
i
]
−
∑
i
=
1
l
−
1
a
[
i
]
\sum_{i=l}^{r}a[i]=\sum_{i=1}^{r}a[i]-\sum_{i=1}^{l-1}a[i]
i=l∑ra[i]=i=1∑ra[i]−i=1∑l−1a[i]
这样就可以写出区间查询的算法。
long long interval_query(int l, int r)
{
return sum(r) - sum(l - 1);
}
通过lowbit()函数,原本查询一个区间需要n次循环,现在变成了logn次,时间复杂度下降了,这就是树状数组的优势。
3. 封装
//若编译失败,请使用最高版本的visual studio
#include <iostream>
#include <vector>
using namespace std;
class BITree : vector<long long>
{
public:
int size;
explicit BITree(int size = 0)
{
this->size = size > 0 ? size : 0;
assign(this->size + 1, 0);
}
void node_add(int loc, long long val)//单点修改
{
if (loc > size || loc < 1) return;
for (int i = loc; i <= size; i += lowbit(i)) (*this)[i] += val;
}
long long query(int l, int r)//区间查询
{
return 1 <= l && l <= r && r <= size ? sum(r) - sum(l - 1) : 0;
}
long long query(int loc)//重载一个单点查询
{
return 1 <= loc && loc <= size ? sum(loc) - sum(loc - 1) : 0;
}
private:
int lowbit(int x)
{
return x & -x;
}
long long sum(int loc)
{
long long s = 0;
for (int i = loc; i >= 1; i -= lowbit(i)) s += (*this)[i];
return s;
}
};
区间修改与单点查询
- 区间修改
如果依然用上述方法建立树状数组,那么区间修改的时间复杂度会非常高,约为O(n*logn)。当我们在做大量数据的区间修改时,这样的时间复杂度是很不理想的,所以我们应该换一种方法建立树状数组。
在这里引入差分的方法,进行差分建树。
规定 a [ i ] = ∑ j = 1 i d [ j ] a[i]=\sum_{j=1}^id[j] a[i]=∑j=1id[j],即 d [ i ] = a [ i ] − a [ i − 1 ] d[i]=a[i]-a[i-1] d[i]=a[i]−a[i−1]
令树状数组c为差分数组d的区间和,也就是说c不再是根据a建立的树状数组,而是根据d用同样方法建立的树状数组。
那么,差分建树有什么好处呢?
比如当进行区间修改时,是将区间中每个元素加上相同的值,这样,区间内相邻两个元素之差并没有改变,因此,我们只需要改变区间首尾端点与区间前后的差值即可。
例如:
位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
a数组 | 1 | 7 | 3 | 4 | 6 | 3 | 5 | 1 |
d数组 | 1 | 6 | -4 | 1 | 2 | -3 | 2 | -4 |
c数组 | 1 | 7 | -4 | 4 | 2 | -1 | 2 | 1 |
现在将区间[3, 6]增加5,新的数组为:
位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
a数组 | 1 | 7 | 8(+5) | 9(+5) | 11(+5) | 8(+5) | 5 | 1 |
d数组 | 1 | 6 | 1(+5) | 1 | 2 | -3 | -3(-5) | -4 |
c数组 | 1 | 7 | 1(+5) | 9(+5) | 2 | -1 | -3(-5) | 1(-5+5) |
可以看出,将a数组区间[l, r]内的所有元素全部加k,等价于将d[l]加k,将d[r + 1]减k
也就是说无论a数组修改的区间有多大,只需更改差分数组d中的两个点。
这样就将原数组a的区间修改转换成了差分数组d的两次单点修改。
这样就可以利用上述的单点修改的算法写出这里区间修改的算法,很显然算法的时间复杂度为O(logn):
void node_add(int loc, long long val)
{
for (int i = loc; i <= size; i += lowbit(i)) c[i] += val;
}
void interval_add(int l, int r, long long val)
{
node_add(l, val);
node_add(r + 1, -val);
}
- 单点查询
由 a [ i ] = ∑ j = 1 i d [ j ] a[i]=\sum_{j=1}^id[j] a[i]=∑j=1id[j],对原数组a的单点查询,就转化成了对差分数组d的区间[1, i]的求和。这就是前文写过的的sum()函数,时间复杂度为O(logn)。
long long sum(int loc)
{
long long s = 0;
for (int i = loc; i >= 1; i -= lowbit(i)) s += c[i];
return s;
}
long long node_query(int loc)
{
return sum(loc);
}
- 封装
//若编译失败,请使用最高版本的visual studio
#include <iostream>
#include <vector>
using namespace std;
class BITree : vector<long long>
{
public:
int size;
explicit BITree(int size = 0)
{
this->size = size > 0 ? size : 0;
assign(this->size + 1, 0);
}
long long node_query(int loc)//单点查询
{
return 1 <= loc && loc <= size ? sum(loc) : 0;
}
void add(int l, int r, long long val)//区间修改
{
if (r < l || l < 1 || r > size) return;
_add(l, val);
_add(r + 1, -val);
}
void add(int loc, long long val)//重载一个单点修改
{
if (loc < 1 || loc > size) return;
_add(loc, val);
_add(loc + 1, -val);
}
private:
int lowbit(int x)
{
return x & -x;
}
void _add(int loc, long long val)
{
for (int i = loc; i <= size; i += lowbit(i)) (*this)[i] += val;
}
long long sum(int loc)
{
long long s = 0;
for (int i = loc; i >= 1; i -= lowbit(i)) s += (*this)[i];
return s;
}
};
区间修改与区间查询
- 区间查询
那么既要区间修改又要区间查询该怎么做呢?
区间查询的基本方法同前文一样,对于任意区间[l, r],先分别求出[1, l]和[1, r]的区间和,再相减。
对于 ∑ i = 1 n a [ i ] \sum_{i=1}^na[i] ∑i=1na[i]的计算依然利用差分的思想。
由 a [ i ] = ∑ j = 1 i d [ j ] a[i]=\sum_{j=1}^id[j] a[i]=∑j=1id[j]可知, ∑ i = 1 n a [ i ] = ∑ i = 1 n ∑ j = 1 i d [ j ] \sum_{i=1}^na[i]=\sum_{i=1}^n\sum_{j=1}^id[j] ∑i=1na[i]=∑i=1n∑j=1id[j],即
∑ i = 1 n a [ i ] = ( d [ 1 ] + d [ 2 ] + ⋯ + d [ n ] ) + ( d [ 1 ] + d [ 2 ] + ⋯ + d [ n − 1 ] ) + ⋯ + d [ 1 ] = n ∗ d [ 1 ] + ( n − 1 ) ∗ d [ 2 ] + ⋯ + 2 ∗ d [ n − 1 ] + d [ n ] = n ∗ ( d [ 1 ] + d [ 2 ] + ⋯ + d [ n ] ) − ( 0 ∗ d [ 1 ] + 1 ∗ d [ 2 ] + ⋯ + ( n − 1 ) ∗ d [ n ] ) = ∑ i = 1 n ( n − i + 1 ) ∗ d [ i ] = n ∗ ∑ i = 1 n d [ i ] − ∑ i = 1 n ( i − 1 ) ∗ d [ i ] \sum_{i=1}^na[i]\\=(d[1]+d[2]+\dots+d[n])+(d[1]+d[2]+\cdots+d[n-1])+\dots+d[1]\\=n*d[1]+(n-1)*d[2]+\dots+2*d[n-1]+d[n]\\=n*(d[1]+d[2]+\dots+d[n])-(0*d[1]+1*d[2]+\dots+(n-1)*d[n])\\=\sum_{i=1}^n(n-i+1)*d[i]\\=n*\sum_{i=1}^nd[i]-\sum_{i=1}^n(i-1)*d[i] ∑i=1na[i]=(d[1]+d[2]+⋯+d[n])+(d[1]+d[2]+⋯+d[n−1])+⋯+d[1]=n∗d[1]+(n−1)∗d[2]+⋯+2∗d[n−1]+d[n]=n∗(d[1]+d[2]+⋯+d[n])−(0∗d[1]+1∗d[2]+⋯+(n−1)∗d[n])=∑i=1n(n−i+1)∗d[i]=n∗∑i=1nd[i]−∑i=1n(i−1)∗d[i]
即 ∑ i = 1 n a [ i ] = ( n + 1 ) ∗ ∑ i = 1 n d [ i ] − ∑ i = 1 n i ∗ d [ i ] \sum_{i=1}^na[i]=(n+1)*\sum_{i=1}^nd[i]-\sum_{i=1}^ni*d[i] i=1∑na[i]=(n+1)∗i=1∑nd[i]−i=1∑ni∗d[i]
在前文,对于 ∑ i = 1 n d [ i ] \sum_{i=1}^nd[i] ∑i=1nd[i]我们维护了一个树状数组c,那么对于 ∑ i = 1 n d [ i ] \sum_{i=1}^nd[i] ∑i=1nd[i]和 ∑ i = 1 n i ∗ d [ i ] \sum_{i=1}^ni*d[i] ∑i=1ni∗d[i],我们需要维护两个树状数组c1和c2,c1是基于d[i]的树状数组,c2是基于i*d[i]的树状数组。时间复杂度为O(logn)
long long sum(int loc)
{
long long s = 0;
for (int i = loc; i > 0; i -= lowbit(i)) s += (loc + 1) * c1[i] - c2[i];
return s;
}
long long inverval_query(int l, int r)
{
return sum(r) - sum(l - 1);
}
- 区间修改
基本方法和前文的区间修改相似,依然是将数组a的区间修改转换为差分数组d的两次单点修改。
只不过由于区间查询需要两个树状数组c1和c2,因此区间修改时要维护这两个树状数组。时间复杂度为O(logn)
void add(int loc, long long val)
{
for (int i = loc; i <= size; i += lowbit(i))
{
c1[i] += val;
c2[i] += loc * val;
}
}
void interval_add(int l, int r, long long val)
{
add(l, val);
add(r + 1, -val);
}
- 封装
//若编译失败,请使用最高版本的visual studio
#include <iostream>
#include <vector>
using namespace std;
class BITree : vector<vector<long long> >
{
public:
int size;
explicit BITree(int size = 0)
{
this->size = size > 0 ? size : 0;
assign(2, vector<long long>(this->size + 1, 0));
}
long long query(int l, int r)//区间查询
{
return 1 <= l && l <= r && r <= size ? sum(r) - sum(l - 1) : 0;
}
long long query(int loc)//重载一个单点查询
{
return 1 <= loc && loc <= size ? sum(loc) - sum(loc - 1) : 0;
}
void add(int l, int r, long long val)//区间修改
{
if (r < l || l < 1 || r > size) return;
_add(l, val);
_add(r + 1, -val);
}
void add(int loc, long long val)//重载一个单点修改
{
if (loc < 1 || loc > size) return;
_add(loc, val);
_add(loc + 1, -val);
}
private:
int lowbit(int x)
{
return x & -x;
}
void _add(int loc, long long val)
{
for (int i = loc; i <= size; i += lowbit(i))
{
(*this)[0][i] += val;
(*this)[1][i] += loc * val;
}
}
long long sum(int loc)
{
long long s = 0;
for (int i = loc; i >= 1; i -= lowbit(i)) s += (loc + 1) * (*this)[0][i] - (*this)[1][i];
return s;
}
};
多维树状数组(以二维树状数组为例)
一维树状数组的定义式为
c
[
i
]
=
∑
j
=
i
−
l
o
w
b
i
t
(
i
)
+
1
i
a
[
j
]
c[i]=\sum_{j=i-lowbit(i)+1}^ia[j]
c[i]=∑j=i−lowbit(i)+1ia[j]
二维树状数组是在树状数组的基础上拓展而来的一个由数字构成的大矩阵。
二维树状数组的定义式为
c
[
x
]
[
y
]
=
∑
i
=
x
−
l
o
w
b
i
t
(
x
)
+
1
x
∑
j
=
y
−
l
o
w
b
i
t
(
y
)
+
1
y
a
[
i
]
[
j
]
c[x][y]=\sum_{i=x-lowbit(x)+1}^x\sum_{j=y-lowbit(y)+1}^ya[i][j]
c[x][y]=∑i=x−lowbit(x)+1x∑j=y−lowbit(y)+1ya[i][j]
如:一维树状数组中,
c[4] = a[1] + a[2] + a[3] + a[4]
c[6] = a[5] + a[6]
则二维树状数组中,
c[4][6] = a[1][5] + a[2][5] + a[3][5] + a[4][5] + a[1][6] + a[2][6] + a[3][6] + a[4][6]
多维树状数组以此类推。
单点修改与区间查询
- 单点修改
对于一维树状数组我们要修改a[i]就要修改所有包含a[i]的c[j],同样的,对于二维树状数组,我们要修改修改a[i][j],就要修改所有包含a[i][j]的c[x][y]。
根据一维树状数组中, a [ i ] a[i] a[i]包含在 c [ i ] , c [ i + 2 k 1 ] , c [ i + 2 k 1 + 2 k 2 ] , c [ i + 2 k 1 + 2 k 2 + 2 k 3 ] … c[i],c[i+2^{k_1}],c[i+2^{k_1}+2^{k_2}],c[i+2^{k_1}+2^{k_2}+2^{k_3}]\dots c[i],c[i+2k1],c[i+2k1+2k2],c[i+2k1+2k2+2k3]…中易知,对于二维树状数组, a [ i ] [ j ] a[i][j] a[i][j]包含在 c [ i ] [ j ] , c [ i + 2 k 1 ] [ j ] , c [ i + 2 k 1 + 2 k 2 ] [ j ] , c [ i + 2 k 1 + 2 k 2 + 2 k 3 ] [ j ] … c[i][j],c[i+2^{k_1}][j],c[i+2^{k_1}+2^{k_2}][j],c[i+2^{k_1}+2^{k_2}+2^{k_3}][j]\dots c[i][j],c[i+2k1][j],c[i+2k1+2k2][j],c[i+2k1+2k2+2k3][j]… c [ i ] [ j + 2 l 1 ] , c [ i + 2 k 1 ] [ j + 2 l 1 ] , c [ i + 2 k 1 + 2 k 2 ] [ j + 2 l 1 ] , c [ i + 2 k 1 + 2 k 2 + 2 k 3 ] [ j + 2 l 1 ] … c[i][j+2^{l_1}],c[i+2^{k_1}][j+2^{l_1}],c[i+2^{k_1}+2^{k_2}][j+2^{l_1}],c[i+2^{k_1}+2^{k_2}+2^{k_3}][j+2^{l_1}]\dots c[i][j+2l1],c[i+2k1][j+2l1],c[i+2k1+2k2][j+2l1],c[i+2k1+2k2+2k3][j+2l1]… c [ i ] [ j + 2 l 1 + 2 l 2 ] , c [ i + 2 k 1 ] [ j + 2 l 1 + 2 l 2 ] , c [ i + 2 k 1 + 2 k 2 ] [ j + 2 l 1 + 2 l 2 ] , c [ i + 2 k 1 + 2 k 2 + 2 k 3 ] [ j + 2 l 1 + 2 l 2 ] … c[i][j+2^{l_1}+2^{l_2}],c[i+2^{k_1}][j+2^{l_1}+2^{l_2}],c[i+2^{k_1}+2^{k_2}][j+2^{l_1}+2^{l_2}],c[i+2^{k_1}+2^{k_2}+2^{k_3}][j+2^{l_1}+2^{l_2}]\dots c[i][j+2l1+2l2],c[i+2k1][j+2l1+2l2],c[i+2k1+2k2][j+2l1+2l2],c[i+2k1+2k2+2k3][j+2l1+2l2]… ⋯ \cdots ⋯中。
如图,以a[2][3]为例
一维树状数组单点修改的代码为:
void node_add(int loc, long long val)
{
for (int i = loc; i <= size; i += lowbit(i)) c[i] += val;
}
二维树状数组是将一维树状数组扩展为矩阵。类比一维树状数组,可以写出二维树状数组单点修改的代码:
void node_add(int x, int y, long long val)
{
for (int i = x; i <= size_x; i += lowbit(i))
for (int j = y; j <= size_y; j += lowbit(j))
c[i][j] += val;
}
- 区间查询
一维树状数组的区间查询:
long long sum(int loc)
{
long long s = 0;
for (int i = loc; i >= 1; i -= lowbit(i)) s += c[i]
return s;
}
long long interval_query(int l, int r)
{
return sum(r) - sum(l - 1);
}
一维数组我们的计算方式是
∑
i
=
l
r
a
[
i
]
=
∑
i
=
1
r
a
[
i
]
−
∑
i
=
1
l
−
1
a
[
i
]
\sum_{i=l}^ra[i]=\sum_{i=1}^ra[i]-\sum_{i=1}^{l-1}a[i]
∑i=lra[i]=∑i=1ra[i]−∑i=1l−1a[i]
类比一维树状数组,根据容斥原理,二维树状数组的区间查询公式应为:
∑
i
=
x
1
x
2
∑
j
=
y
1
y
2
a
[
i
]
[
j
]
=
∑
i
=
1
x
2
∑
j
=
1
y
2
a
[
i
]
[
j
]
−
∑
i
=
1
x
2
∑
j
=
1
y
1
−
1
a
[
i
]
[
j
]
−
∑
i
=
1
x
1
−
1
∑
j
=
1
y
2
a
[
i
]
[
j
]
+
∑
i
=
1
x
1
−
1
∑
j
=
1
y
1
−
1
a
[
i
]
[
j
]
\sum_{i=x_1}^{x_2}\sum_{j=y_1}^{y_2}a[i][j]=\sum_{i=1}^{x_2}\sum_{j=1}^{y_2}a[i][j]-\sum_{i=1}^{x_2}\sum_{j=1}^{y_1-1}a[i][j]-\sum_{i=1}^{x_1-1}\sum_{j=1}^{y_2}a[i][j]+\sum_{i=1}^{x_1-1}\sum_{j=1}^{y_1-1}a[i][j]
i=x1∑x2j=y1∑y2a[i][j]=i=1∑x2j=1∑y2a[i][j]−i=1∑x2j=1∑y1−1a[i][j]−i=1∑x1−1j=1∑y2a[i][j]+i=1∑x1−1j=1∑y1−1a[i][j]
long long sum(int x, int y)
{
long long s = 0;
for (int i = x; i >= 1; i -= lowbit(i))
for (int j = y; j >= 1; j -= lowbit(j))
s += c[i][j];
return s;
}
long long interval_query(int x1, int y1, int x2, int y2)
{
return sum(x2, y2) - sum(x2, y1 - 1) - sum(x1 - 1, y2) + sum(x1 - 1, y1 - 1);
}
- 封装
//若编译失败,请使用最高版本的visual studio
#include <iostream>
#include <vector>
using namespace std;
class BITree : vector<vector<long long> >
{
public:
int size_x, size_y;
explicit BITree(int x = 0, int y = 0)
{
size_x = x > 0 ? x : 0;
size_y = y > 0 ? y : 0;
assign(size_x + 1, vector<long long>(size_y + 1, 0));
}
long long query(int x1, int y1, int x2, int y2)//区间查询
{
if (y1 > y2) std::swap(y1, y2);
if (x1 > x2) std::swap(x1, x2);
return 1 <= x1 && x2 <= size_x && 1 <= y1 && y2 <= size_y ? sum(x2, y2) - sum(x2, y1 - 1) - sum(x1 - 1, y2) + sum(x1 - 1, y1 - 1) : 0;
}
long long query(int x, int y)//重载一个单点查询
{
return 1 <= x && x <= size_x && 1 <= y && y <= size_y ? sum(x, y) - sum(x, y - 1) - sum(x - 1, y) + sum(x - 1, y - 1) : 0;
}
void node_add(int x, int y, long long val)//单点修改
{
if (x < 1 || y < 1 || x > size_x || y > size_y) return;
for (int i = x; i <= size_x; i += lowbit(i))
for (int j = y; j <= size_y; j += lowbit(j))
(*this)[i][j] += val;
}
private:
int lowbit(int x)
{
return x & -x;
}
long long sum(int x, int y)
{
long long s = 0;
for (int i = x; i >= 1; i -= lowbit(i))
for (int j = y; j >= 1; j -= lowbit(j))
s += (*this)[i][j];
return s;
}
};
区间修改与单点查询
与一维树状数组相似,二维树状数组的区间修改依然使用差分的思想。
规定
a
[
x
]
[
y
]
=
∑
i
=
1
x
∑
j
=
1
y
d
[
i
]
[
j
]
a[x][y]=\sum_{i=1}^x\sum_{j=1}^yd[i][j]
a[x][y]=∑i=1x∑j=1yd[i][j],则
d
[
i
]
[
j
]
=
a
[
i
]
[
j
]
+
a
[
i
−
1
]
[
j
−
1
]
−
a
[
i
]
[
j
−
1
]
−
a
[
i
−
1
]
[
j
]
d[i][j]=a[i][j]+a[i-1][j-1]-a[i][j-1]-a[i-1][j]
d[i][j]=a[i][j]+a[i−1][j−1]−a[i][j−1]−a[i−1][j]
- 区间修改
如图
将(2, 1)到(3, 3)的矩阵内所有元素加3,变化后的数组为
当a数组的矩阵整体改变时,无论是多大的矩阵,只需要改变d数组的四个点即可,这样就又将a数组的区间修改转化成了差分数组d的单点修改。
一维树状数组的区间修改代码:
void node_add(int loc, long long val)
{
for (int i = loc; i <= size; i += lowbit(i)) c[i] += val;
}
void interval_add(int l, int r, long long val)
{
node_add(l, val);
node_add(r + 1, -val);
}
类比一维树状数组,二维树状数组区间修改代码:
void node_add(int x, int y, long long val)
{
for (int i = x; i <= size_x; i += lowbit(i))
for (int j = y; j <= size_y; j += lowbit(j))
c[i][j] += val;
}
void interval_add(int x1, int y1, int x2, int y2, long long val)
{
node_add(x1, y1, val);
node_add(x1, y2 + 1, -val);
node_add(x2 + 1, y1, -val);
node_add(x2 + 1, y2 + 1, val);
}
- 单点查询
一维树状数组的单点查询:
long long sum(int loc)
{
long long s = 0;
for (int i = loc; i >= 1; i -= lowbit(i)) s += c[i];
return s;
}
long long node_query(int loc)
{
return sum(loc);
}
由 a [ x ] [ y ] = ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] a[x][y]=\sum_{i=1}^x\sum_{j=1}^yd[i][j] a[x][y]=∑i=1x∑j=1yd[i][j],类比一维树状数组的单点查询,二维树状数组单点查询代码:
long long sum(int x, int y)
{
long long s = 0;
for (int i = x; i >= 1; i -= lowbit(i))
for (int j = y; j >= 1; j -= lowbit(j))
s += c[i][j];
return s;
}
long long node_query(int x, int y)
{
return sum(x, y);
}
- 封装
//若编译失败,请使用最高版本的visual studio
#include <iostream>
#include <vector>
using namespace std;
class BITree : vector<vector<long long> >
{
public:
int size_x, size_y;
explicit BITree(int x = 0, int y = 0)
{
size_x = x > 0 ? x : 0;
size_y = y > 0 ? y : 0;
assign(size_x + 1, vector<long long>(size_y + 1, 0));
}
long long node_query(int x, int y)//单点查询
{
return 1 <= x && x <= size_x && 1 <= y && y <= size_y ? sum(x, y) : 0;
}
void add(int x1, int y1, int x2, int y2, long long val)//区间修改
{
if (x1 > x2) std::swap(x1, x2);
if (y1 > y2) std::swap(y1, y2);
if (x1 < 1 || y1 < 1 || x2 > size_x || y2 > size_y) return;
_add(x1, y1, val);
_add(x1, y2 + 1, -val);
_add(x2 + 1, y1, -val);
_add(x2 + 1, y2 + 1, val);
}
void add(int x, int y, long long val)//重载一个单点修改
{
if (x < 1 || y < 1 || x > size_x || y > size_y) return;
_add(x, y, val);
_add(x, y + 1, -val);
_add(x + 1, y, -val);
_add(x + 1, y + 1, val);
}
private:
int lowbit(int x)
{
return x & -x;
}
void _add(int x, int y, long long val)
{
for (int i = x; i <= size_x; i += lowbit(i))
for (int j = y; j <= size_y; j += lowbit(j))
(*this)[i][j] += val;
}
long long sum(int x, int y)
{
long long s = 0;
for (int i = x; i >= 1; i -= lowbit(i))
for (int j = y; j >= 1; j -= lowbit(j))
s += (*this)[i][j];
return s;
}
};
区间修改与区间查询
- 区间查询
一维树状数组区间和公式为: ∑ i = 1 n a [ i ] = ∑ i = 1 n ∑ j = 1 i d [ j ] \sum_{i=1}^na[i]=\sum_{i=1}^n\sum_{j=1}^id[j] i=1∑na[i]=i=1∑nj=1∑id[j]
二维树状数组区间和公式为: ∑ u = 1 x ∑ v = 1 y a [ u ] [ v ] = ∑ u = 1 x ∑ v = 1 y ∑ i = 1 u ∑ j = 1 v d [ i ] [ j ] \sum_{u=1}^x\sum_{v=1}^ya[u][v]=\sum_{u=1}^x\sum_{v=1}^y\sum_{i=1}^u\sum_{j=1}^vd[i][j] u=1∑xv=1∑ya[u][v]=u=1∑xv=1∑yi=1∑uj=1∑vd[i][j]
一维树状数组进行了如下转换 ∑ i = 1 n a [ i ] = ∑ i = 1 n ∑ j = 1 i d [ j ] = ∑ i = 1 n ( n + 1 − i ) ∗ d [ i ] = ( n + 1 ) ∗ ∑ i = 1 n d [ i ] − ∑ i = 1 n i ∗ d [ i ] \sum_{i=1}^na[i]=\sum_{i=1}^n\sum_{j=1}^id[j]=\sum_{i=1}^n(n+1-i)*d[i]=(n+1)*\sum_{i=1}^nd[i]-\sum_{i=1}^ni*d[i] i=1∑na[i]=i=1∑nj=1∑id[j]=i=1∑n(n+1−i)∗d[i]=(n+1)∗i=1∑nd[i]−i=1∑ni∗d[i]
对于二维树状数组,我们也要将 ∑ u = 1 x ∑ v = 1 y a [ u ] [ v ] = ∑ u = 1 x ∑ v = 1 y ∑ i = 1 u ∑ j = 1 v d [ i ] [ j ] \sum_{u=1}^x\sum_{v=1}^ya[u][v]=\sum_{u=1}^x\sum_{v=1}^y\sum_{i=1}^u\sum_{j=1}^vd[i][j] ∑u=1x∑v=1ya[u][v]=∑u=1x∑v=1y∑i=1u∑j=1vd[i][j]拆分成 ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] \sum_{i=1}^x\sum_{j=1}^yd[i][j] ∑i=1x∑j=1yd[i][j]的形式
∑ u = 1 x ∑ v = 1 y a [ u ] [ v ] = ∑ u = 1 x ∑ v = 1 y ∑ i = 1 u ∑ j = 1 v d [ i ] [ j ] = x ∗ y ∗ d [ 1 ] [ 1 ] + x ∗ ( y − 1 ) ∗ d [ 1 ] [ 2 ] + ( x − 1 ) ∗ y ∗ d [ 2 ] [ 1 ] + ⋯ + 1 ∗ 1 ∗ d [ x ] [ y ] = ∑ i = 1 x ∑ j = 1 y ( x + 1 − i ) ∗ ( y + 1 − j ) d [ i ] [ j ] \sum_{u=1}^x\sum_{v=1}^ya[u][v]\\=\sum_{u=1}^x\sum_{v=1}^y\sum_{i=1}^u\sum_{j=1}^vd[i][j]\\=x*y*d[1][1]+x*(y-1)*d[1][2]+(x-1)*y*d[2][1]+\dots+1*1*d[x][y]\\=\sum_{i=1}^x\sum_{j=1}^y(x+1-i)*(y+1-j)d[i][j] ∑u=1x∑v=1ya[u][v]=∑u=1x∑v=1y∑i=1u∑j=1vd[i][j]=x∗y∗d[1][1]+x∗(y−1)∗d[1][2]+(x−1)∗y∗d[2][1]+⋯+1∗1∗d[x][y]=∑i=1x∑j=1y(x+1−i)∗(y+1−j)d[i][j]
即 ∑ i = 1 x ∑ j = 1 y a [ i ] [ j ] = ( x ∗ y + x + y + 1 ) ∗ ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] + ∑ i = 1 x ∑ j = 1 y ( i ∗ j − i − j ) ∗ d [ i ] [ j ] − x ∗ ∑ i = 1 x ∑ j = 1 y j ∗ d [ i ] [ j ] − y ∗ ∑ i = 1 x ∑ j = 1 y i ∗ d [ i ] [ j ] \sum_{i=1}^x\sum_{j=1}^ya[i][j]\\=(x*y+x+y+1)*\sum_{i=1}^x\sum_{j=1}^yd[i][j]+\sum_{i=1}^x\sum_{j=1}^y(i*j-i-j)*d[i][j]-x*\sum_{i=1}^x\sum_{j=1}^yj*d[i][j]-y*\sum_{i=1}^x\sum_{j=1}^yi*d[i][j] ∑i=1x∑j=1ya[i][j]=(x∗y+x+y+1)∗∑i=1x∑j=1yd[i][j]+∑i=1x∑j=1y(i∗j−i−j)∗d[i][j]−x∗∑i=1x∑j=1yj∗d[i][j]−y∗∑i=1x∑j=1yi∗d[i][j]
需要维护四个二维树状数组c1、c2、c3、c4
一维树状数组的区间查询:
long long sum(int loc)
{
long long s = 0;
for (int i = loc; i > 0; i -= lowbit(i)) s += (loc + 1) * c1[i] - c2[i];
return s;
}
long long inverval_query(int l, int r)
{
return sum(r) - sum(l - 1);
}
类比一维树状数组,二维树状数组的区间查询:
long long sum(int x, int y)
{
long long s = 0;
for (int i = x; i > 0; i -= lowbit(i))
for (int j = y; j > 0; j -= lowbit(j))
s += (x * y + x + y + 1) * c1[i][j] + c2[i][j] - x * c3[i][j] - y * c4[i][j];
return s;
}
long long inverval_query(int x1, int y1, int x2, int y2)
{
return sum(x2, y2) - sum(x2, y1 - 1) - sum(x1 - 1, y2) + sum(x1 - 1, y1 - 1);
}
- 区间修改
一维树状数组的区间修改:
void add(int loc, long long val)
{
for (int i = loc; i <= size; i += lowbit(i))
{
c1[i] += val;
c2[i] += loc * val;
}
}
void interval_add(int l, int r, long long val)
{
add(l, val);
add(r + 1, -val);
}
类比一维树状数组的区间修改,二维树状数组的区间修改:
void add(int x, int y, long long val)
{
for (int i = x; i <= size_x; i += lowbit(i))
for (int j = y; j <= size_y; j += lowbit(j))
{
c1[i][j] += val;
c2[i][j] += (x * y - x - y) * val;
c3[i][j] += y * val;
c4[i][j] += x * val;
}
}
void interval_add(int x1, int y1, int x2, int y2, long long val)
{
add(x1, y1, val);
add(x1, y2 + 1, -val);
add(x2 + 1, y1, -val);
add(x2 + 1, y2 + 1, val);
}
- 封装
//若编译失败,请使用最高版本的visual studio
#include <iostream>
#include <vector>
using namespace std;
class BITree : vector<vector<vector<long long> > >
{
public:
int size_x, size_y;
explicit BITree(int x = 0, int y = 0)
{
size_x = x > 0 ? x : 0;
size_y = y > 0 ? y : 0;
assign(4, vector<vector<long long> >(size_x + 1, vector<long long>(size_y + 1, 0)));
}
void add(int x1, int y1, int x2, int y2, long long val)//区间修改
{
if (x1 > x2) std::swap(x1, x2);
if (y1 > y2) std::swap(y1, y2);
if (x1 < 1 || y1 < 1 || x2 > size_x || y2 > size_y) return;
_add(x1, y1, val);
_add(x1, y2 + 1, -val);
_add(x2 + 1, y1, -val);
_add(x2 + 1, y2 + 1, val);
}
void add(int x, int y, long long val)//重载一个单点修改
{
if (x < 1 || y < 1 || x > size_x || y > size_y) return;
_add(x, y, val);
_add(x, y + 1, -val);
_add(x + 1, y, -val);
_add(x + 1, y + 1, val);
}
long long query(int x1, int y1, int x2, int y2)//区间查询
{
if (x1 > x2) std::swap(x1, x2);
if (y1 > y2) std::swap(y1, y2);
return 1 <= x1 && x2 <= size_x && 1 <= y1 && y2 <= size_y ? sum(x2, y2) - sum(x2, y1 - 1) - sum(x1 - 1, y2) + sum(x1 - 1, y1 - 1) : 0;
}
long long query(int x, int y)//重载一个单点查询
{
return 1 <= x && x <= size_x && 1 <= y && y <= size_y ? sum(x, y) - sum(x, y - 1) - sum(x - 1, y) + sum(x - 1, y - 1) : 0;
}
private:
int lowbit(int x)
{
return x & -x;
}
long long sum(int x, int y)
{
long long s = 0;
for (int i = x; i > 0; i -= lowbit(i))
for (int j = y; j > 0; j -= lowbit(j))
s += (*this)[0][i][j] * (x * y + x + y + 1) + (*this)[1][i][j] - x * (*this)[2][i][j] - y * (*this)[3][i][j];
return s;
}
void _add(int x, int y, long long val)
{
for (int i = x; i <= size_x; i += lowbit(i))
for (int j = y; j <= size_y; j += lowbit(j))
{
(*this)[0][i][j] += val;
(*this)[1][i][j] += val * (x * y - x - y);
(*this)[2][i][j] += val * y;
(*this)[3][i][j] += val * x;
}
}
};
N维树状数组简述
一维树状数组的定义式为
c
[
i
]
=
∑
j
=
i
−
l
o
w
b
i
t
(
i
)
+
1
i
a
[
j
]
c[i]=\sum_{j=i-lowbit(i)+1}^ia[j]
c[i]=∑j=i−lowbit(i)+1ia[j]
二维树状数组的定义式为
c
[
x
]
[
y
]
=
∑
i
=
x
−
l
o
w
b
i
t
(
x
)
+
1
x
∑
j
=
y
−
l
o
w
b
i
t
(
y
)
+
1
y
a
[
i
]
[
j
]
c[x][y]=\sum_{i=x-lowbit(x)+1}^x\sum_{j=y-lowbit(y)+1}^ya[i][j]
c[x][y]=∑i=x−lowbit(x)+1x∑j=y−lowbit(y)+1ya[i][j]
N维树状数组定义式为
c
[
x
1
]
[
x
2
]
⋯
[
x
n
]
=
∑
i
1
=
x
1
−
l
o
w
b
i
t
(
x
1
)
+
1
x
1
∑
i
2
=
x
2
−
l
o
w
b
i
t
(
x
2
)
+
1
x
2
⋯
∑
i
n
=
x
n
−
l
o
w
b
i
t
(
x
n
)
+
1
x
n
a
[
i
1
]
[
i
2
]
⋯
[
i
n
]
c[x_1][x_2]\cdots[x_n]=\sum_{i_1=x_1-lowbit(x_1)+1}^{x_1}\sum_{i_2=x_2-lowbit(x_2)+1}^{x_2}\dots\sum_{i_n=x_n-lowbit(x_n)+1}^{x_n}a[i_1][i_2]\cdots[i_n]
c[x1][x2]⋯[xn]=∑i1=x1−lowbit(x1)+1x1∑i2=x2−lowbit(x2)+1x2⋯∑in=xn−lowbit(xn)+1xna[i1][i2]⋯[in]
单点修改与区间查询
- 单点修改
一维树状数组是一层循环:
void node_add(int loc, long long val)
{
for (int i = loc; i <= size; i += lowbit(i)) c[i] += val;
}
二维树状数组是两层循环:
void node_add(int x, int y, long long val)
{
for (int i = x; i <= size_x; i += lowbit(i))
for (int j = y; j <= size_y; j += lowbit(j))
c[i][j] += val;
}
N维树状数组应该是N层循环。
- 区间查询
一维树状数组,sum()函数是一层循环,公式是 ∑ i = l r a [ i ] = ∑ i = 1 r a [ i ] − ∑ i = 1 l − 1 \sum_{i=l}^ra[i]=\sum_{i=1}^ra[i]-\sum_{i=1}^{l-1} ∑i=lra[i]=∑i=1ra[i]−∑i=1l−1
long long sum(int loc)
{
long long s = 0;
for (int i = loc; i >= 1; i -= lowbit(i)) s += c[i]
return s;
}
long long interval_query(int l, int r)
{
return sum(r) - sum(l - 1);
}
二维树状数组,sum()函数是两层循环,公式是 ∑ i = x 1 x 2 ∑ j = y 1 y 2 a [ i ] [ j ] = ∑ i = 1 x 2 ∑ j = 1 y 2 a [ i ] [ j ] − ∑ i = 1 x 2 ∑ j = 1 y 1 − 1 a [ i ] [ j ] − ∑ i = 1 x 1 − 1 ∑ j = 1 y 2 a [ i ] [ j ] + ∑ i = 1 x 1 − 1 ∑ j = 1 y 1 − 1 a [ i ] [ j ] \sum_{i=x_1}^{x_2}\sum_{j=y_1}^{y_2}a[i][j]=\sum_{i=1}^{x_2}\sum_{j=1}^{y_2}a[i][j]-\sum_{i=1}^{x_2}\sum_{j=1}^{y_1-1}a[i][j]-\sum_{i=1}^{x_1-1}\sum_{j=1}^{y_2}a[i][j]+\sum_{i=1}^{x_1-1}\sum_{j=1}^{y_1-1}a[i][j] i=x1∑x2j=y1∑y2a[i][j]=i=1∑x2j=1∑y2a[i][j]−i=1∑x2j=1∑y1−1a[i][j]−i=1∑x1−1j=1∑y2a[i][j]+i=1∑x1−1j=1∑y1−1a[i][j]
long long sum(int x, int y)
{
long long s = 0;
for (int i = x; i >= 1; i -= lowbit(i))
for (int j = y; j >= 1; j -= lowbit(j))
s += c[i][j];
return s;
}
long long interval_query(int x1, int y1, int x2, int y2)
{
return sum(x2, y2) - sum(x2, y1 - 1) - sum(x1 - 1, y2) + sum(x1 - 1, y1 - 1);
}
N维树状数组,sum()函数应该是N层循环,公式依然是根据容斥原理推导。
如三维树状数组:
∑
i
=
x
1
x
2
∑
j
=
y
1
y
2
∑
k
=
z
1
z
2
a
[
i
]
[
j
]
[
k
]
=
∑
i
=
1
x
2
∑
j
=
1
y
2
∑
k
=
1
z
2
a
[
i
]
[
j
]
[
k
]
−
∑
i
=
1
x
1
−
1
∑
j
=
1
y
2
∑
k
=
1
z
2
a
[
i
]
[
j
]
[
k
]
−
∑
i
=
1
x
2
∑
j
=
1
y
1
−
1
∑
k
=
1
z
2
a
[
i
]
[
j
]
[
k
]
−
∑
i
=
1
x
2
∑
j
=
1
y
2
∑
k
=
1
z
1
−
1
a
[
i
]
[
j
]
[
k
]
+
∑
i
=
1
x
1
−
1
∑
j
=
1
y
1
−
1
∑
k
=
1
z
2
a
[
i
]
[
j
]
[
k
]
+
∑
i
=
1
x
1
−
1
∑
j
=
1
y
2
∑
k
=
1
z
1
−
1
a
[
i
]
[
j
]
[
k
]
+
∑
i
=
1
x
2
∑
j
=
1
y
1
−
1
∑
k
=
1
z
1
−
1
a
[
i
]
[
j
]
[
k
]
−
∑
i
=
1
x
1
−
1
∑
j
=
1
y
1
−
1
∑
k
=
1
z
1
−
1
a
[
i
]
[
j
]
[
k
]
\sum_{i=x_1}^{x_2}\sum_{j=y_1}^{y_2}\sum_{k=z_1}^{z_2}a[i][j][k]=\sum_{i=1}^{x_2}\sum_{j=1}^{y_2}\sum_{k=1}^{z_2}a[i][j][k]-\sum_{i=1}^{x_1-1}\sum_{j=1}^{y_2}\sum_{k=1}^{z_2}a[i][j][k]-\sum_{i=1}^{x_2}\sum_{j=1}^{y_1-1}\sum_{k=1}^{z_2}a[i][j][k]-\sum_{i=1}^{x_2}\sum_{j=1}^{y_2}\sum_{k=1}^{z_1-1}a[i][j][k]+\sum_{i=1}^{x_1-1}\sum_{j=1}^{y_1-1}\sum_{k=1}^{z_2}a[i][j][k]+\sum_{i=1}^{x_1-1}\sum_{j=1}^{y_2}\sum_{k=1}^{z_1-1}a[i][j][k]+\sum_{i=1}^{x_2}\sum_{j=1}^{y_1-1}\sum_{k=1}^{z_1-1}a[i][j][k]-\sum_{i=1}^{x_1-1}\sum_{j=1}^{y_1-1}\sum_{k=1}^{z_1-1}a[i][j][k]
∑i=x1x2∑j=y1y2∑k=z1z2a[i][j][k]=∑i=1x2∑j=1y2∑k=1z2a[i][j][k]−∑i=1x1−1∑j=1y2∑k=1z2a[i][j][k]−∑i=1x2∑j=1y1−1∑k=1z2a[i][j][k]−∑i=1x2∑j=1y2∑k=1z1−1a[i][j][k]+∑i=1x1−1∑j=1y1−1∑k=1z2a[i][j][k]+∑i=1x1−1∑j=1y2∑k=1z1−1a[i][j][k]+∑i=1x2∑j=1y1−1∑k=1z1−1a[i][j][k]−∑i=1x1−1∑j=1y1−1∑k=1z1−1a[i][j][k]
区间修改与单点查询
N维树状数组依然会采用差分的思想。
一维树状数组差分公式:
a
[
i
]
=
∑
j
=
1
i
d
[
j
]
a[i]=\sum_{j=1}^id[j]
a[i]=∑j=1id[j],即
d
[
i
]
=
a
[
i
]
−
a
[
i
−
1
]
d[i]=a[i]-a[i-1]
d[i]=a[i]−a[i−1]
二维树状数组差分公式:
a
[
x
]
[
y
]
=
∑
i
=
1
x
∑
j
=
1
y
d
[
i
]
[
j
]
a[x][y]=\sum_{i=1}^x\sum_{j=1}^yd[i][j]
a[x][y]=∑i=1x∑j=1yd[i][j],即
d
[
i
]
[
j
]
=
a
[
i
]
[
j
]
+
a
[
i
−
1
]
[
j
−
1
]
−
a
[
i
]
[
j
−
1
]
−
a
[
i
−
1
]
[
j
]
d[i][j]=a[i][j]+a[i-1][j-1]-a[i][j-1]-a[i-1][j]
d[i][j]=a[i][j]+a[i−1][j−1]−a[i][j−1]−a[i−1][j]
可以推出三维树状数组差分公式:
a
[
x
]
[
y
]
[
z
]
=
∑
i
=
1
x
∑
j
=
1
y
∑
k
=
1
z
d
[
i
]
[
j
]
[
k
]
a[x][y][z]=\sum_{i=1}^x\sum_{j=1}^y\sum_{k=1}^zd[i][j][k]
a[x][y][z]=∑i=1x∑j=1y∑k=1zd[i][j][k],即
d
[
i
]
[
j
]
[
k
]
=
a
[
i
]
[
j
]
[
k
]
−
a
[
i
−
1
]
[
j
]
[
k
]
−
a
[
i
]
[
j
−
1
]
[
k
]
−
a
[
i
]
[
j
]
[
k
−
1
]
+
a
[
i
−
1
]
[
j
−
1
]
[
k
]
+
a
[
i
−
1
]
[
j
]
[
k
−
1
]
+
a
[
i
]
[
j
−
1
]
[
k
−
1
]
−
a
[
i
−
1
]
[
j
−
1
]
[
k
−
1
]
d[i][j][k]=a[i][j][k]-a[i-1][j][k]-a[i][j-1][k]-a[i][j][k-1]+a[i-1][j-1][k]+a[i-1][j][k-1]+a[i][j-1][k-1]-a[i-1][j-1][k-1]
d[i][j][k]=a[i][j][k]−a[i−1][j][k]−a[i][j−1][k]−a[i][j][k−1]+a[i−1][j−1][k]+a[i−1][j][k−1]+a[i][j−1][k−1]−a[i−1][j−1][k−1]
公式形式类似容斥原理公式。
因此N维树状数组的差分公式可根据容斥原理写出。
为什么会与容斥原理形式一致呢?因为a数组相当于d数组的区间求和,d数组中一个元素,相当于a数组的特殊的区间查询,之所以特殊,是因为这个区间只有一个点,而且a数组并不是树状数组。区间查询本身就是容斥原理,因此差分公式与容斥原理形式一致。
- 区间修改
一维树状数组将区间[l, r]的修改转换成了[l , r + 1]的两个端点的单点修改。
void interval_add(int l, int r, long long val)
{
node_add(l, val);
node_add(r + 1, -val);
}
二维树状数组将矩阵[x1, y1, x2, y2]的区间修改转换成了矩阵[x1, y1, x2 + 1, y2 + 1]四个顶点的单点修改。
void interval_add(int x1, int y1, int x2, int y2, long long val)
{
node_add(x1, y1, val);
node_add(x1, y2 + 1, -val);
node_add(x2 + 1, y1, -val);
node_add(x2 + 1, y2 + 1, val);
}
可以推出,三维树状数组应该会将长方体[x1, y1, z1, x2, y2, z2]的区间修改转换成对长方体[x1, y1, z1, x2 + 1, y2 + 1, z2 + 1]的八个顶点的单点修改。
N维树状数组类似,将
[
x
11
,
x
21
,
…
,
x
n
1
,
x
12
,
x
22
,
…
,
x
n
2
]
[x_{11},x_{21},\dots,x_{n1},x_{12},x_{22},\dots,x_{n2}]
[x11,x21,…,xn1,x12,x22,…,xn2]的区间修改转换成
[
x
11
,
x
21
,
…
,
x
n
1
,
x
12
+
1
,
x
22
+
1
,
…
,
x
n
2
+
1
]
[x_{11},x_{21},\dots,x_{n1},x_{12}+1,x_{22}+1,\dots,x_{n2}+1]
[x11,x21,…,xn1,x12+1,x22+1,…,xn2+1]的
2
N
2^N
2N个顶点的单点修改。
- 单点查询
一维树状数组将点loc的单点查询转换成了对区间[1, loc]的区间查询。
long long node_query(int loc)
{
return sum(loc);
}
二维树状数组将点(x, y)的单点查询转换成了对矩阵[1, 1, x, y]的区间查询。
long long node_query(int x, int y)
{
return sum(x, y);
}
三位树状数组应该是将点(x1, y1, z1)的单点查询转换成对长方体[1, 1, 1, x1, y1, z1]的区间查询。
N维树状数组类似,将
(
x
1
,
x
2
,
…
,
x
n
)
(x_{1},x_{2},\dots,x_{n})
(x1,x2,…,xn)的单点查询转换成对
[
1
,
1
,
…
,
1
,
x
1
,
x
2
,
…
,
x
n
]
[1,1,\dots,1,x_{1},x_{2},\dots,x_{n}]
[1,1,…,1,x1,x2,…,xn]的
2
N
2^N
2N个顶点的区间查询。
区间修改与区间查询
- 区间查询
一维树状数组进行了如下转换: ∑ i = 1 n a [ i ] = ∑ i = 1 n ( n + 1 − i ) ∗ d [ i ] \sum_{i=1}^na[i]=\sum_{i=1}^n(n+1-i)*d[i] ∑i=1na[i]=∑i=1n(n+1−i)∗d[i]
二维树状数组进行了如下转换: ∑ i = 1 x ∑ j = 1 y a [ x ] [ y ] = ∑ i = 1 x ∑ j = 1 y ( x + 1 − i ) ∗ ( y + 1 − j ) d [ i ] [ j ] \sum_{i=1}^x\sum_{j=1}^ya[x][y]=\sum_{i=1}^x\sum_{j=1}^y(x+1-i)*(y+1-j)d[i][j] ∑i=1x∑j=1ya[x][y]=∑i=1x∑j=1y(x+1−i)∗(y+1−j)d[i][j]
三维树状数组应该进行如下转换: ∑ i = 1 x ∑ j = 1 y ∑ k = 1 z a [ x ] [ y ] [ z ] = ∑ i = 1 x ∑ j = 1 y ∑ k = 1 z ( x + 1 − i ) ∗ ( y + 1 − j ) ∗ ( z + 1 − k ) d [ i ] [ j ] [ k ] \sum_{i=1}^x\sum_{j=1}^y\sum_{k=1}^za[x][y][z]=\sum_{i=1}^x\sum_{j=1}^y\sum_{k=1}^z(x+1-i)*(y+1-j)*(z+1-k)d[i][j][k] i=1∑xj=1∑yk=1∑za[x][y][z]=i=1∑xj=1∑yk=1∑z(x+1−i)∗(y+1−j)∗(z+1−k)d[i][j][k]
观察一下就可以推出N维树状数组的转换公式。
N维树状数组的区间查询依然依据容斥原理,依然要维护多个树状数组。
一维树状数组需要维护两个,二维树状数组需要维护四个,可以推测:N维树状数组需要维护 2 N 2^N 2N个。 - 区间修改
区间修改同前文。
树状数组的应用与习题
未完待续…