1 介绍
给出一个长为 n n n 的数组 a a a,进行若干次几下操作:
- 改变 a x a_x ax 的值
- 求 ∑ i = 1 x a i \sum_{i=1}^xa_i ∑i=1xai 并输出
树状数组是一种树形结构的数组,以二叉树的方式进行存储。它的基本操作有:改变元素的值、求区间前缀和。
树状数组的优越性在于其进行操作的复杂度都是 O ( n l o g n ) O(nlogn) O(nlogn).
2 前置内容
求一个数的二进制从右往左第一个
1
\texttt 1
1 与后面末尾
0
\texttt 0
0 组成的数 lowbit()
.
例如: l o w b i t ( 4 ) = 4 , l o w b i t ( 7 ) = 1 lowbit(4)=4,lowbit(7)=1 lowbit(4)=4,lowbit(7)=1.
实现方法:
void lowbit(int n) {
return n & (-n);
}
//other examples
//return n & (~n + 1);
//return n & (n ^ (n - 1));
3 实现
以一个长度为 8 8 8 的数组 a a a 举例:
现将数组 a a a 的所有元素都放到二叉树的叶子结点上,然后进行操作,得到树状数组 c c c.
c 1 = a 1 ( 1 ) 10 = ( 0001 ) 2 c_1=a_1 \ \ \ \ (1)_{10}=(0001)_2 c1=a1 (1)10=(0001)2
c 2 = a 1 + a 2 ( 2 ) 10 = ( 0010 ) 2 c_2=a_1+a_2 \ \ \ \ (2)_{10}=(0010)_2 c2=a1+a2 (2)10=(0010)2
c 3 = a 3 ( 3 ) 10 = ( 0011 ) 2 c_3=a_3 \ \ \ \ (3)_{10}=(0011)_2 c3=a3 (3)10=(0011)2
c 4 = a 1 + a 2 + a 3 + a 4 ( 4 ) 10 = ( 0100 ) 2 c4=a_1+a_2+a_3+a_4 \ \ \ \ (4)_{10}=(0100)_2 c4=a1+a2+a3+a4 (4)10=(0100)2
c 5 = a 5 ( 5 ) 10 = ( 0101 ) 2 c_5=a_5 \ \ \ \ (5)_{10}=(0101)_2 c5=a5 (5)10=(0101)2
c 6 = a 5 + a 6 ( 6 ) 10 = ( 0110 ) 2 c_6=a_5+a_6 \ \ \ \ (6)_{10}=(0110)_2 c6=a5+a6 (6)10=(0110)2
c 7 = a 7 ( 7 ) 10 = ( 0111 ) 2 c_7=a_7 \ \ \ \ (7)_{10}=(0111)_2 c7=a7 (7)10=(0111)2
c 8 = a 1 + a 2 + a 3 + a 4 + a 5 + a 6 + a 7 + a 8 ( 8 ) 10 = ( 1000 ) 2 c_8=a_1+a_2+a_3+a_4+a_5+a_6+a_7+a_8 \ \ \ \ (8)_{10}=(1000)_2 c8=a1+a2+a3+a4+a5+a6+a7+a8 (8)10=(1000)2
二叉树如下:
我们可以发现:
从下往上第 i i i 层的编号二进制的末尾都是 ( i − 1 ) (i-1) (i−1) 个 0 \texttt 0 0,也就是从右往左第一个 1 \texttt 1 1 所在第 i i i 位。
如果要改变某个元素的值( a x + y a_x+y ax+y),如 a 3 a_3 a3 ,那么同样要改动 c 3 , c 4 , c 8 c_3,c_4,c_8 c3,c4,c8 的值。观察这三个元素下标的规律,我们可以发现:
3 + l o w b i t ( 3 ) = 4 , 4 + l o w b i t ( 4 ) = 8 3+lowbit(3)=4,4+lowbit(4)=8 3+lowbit(3)=4,4+lowbit(4)=8
可得代码:
void change(int x, int y) {
for (int i = x;i <= n; i += lowbit(i)) c[cnt] += y;
}
如果要求 ∑ i = 1 x a i \sum_{i=1}^x a_i ∑i=1xai,如求从 a 1 a_1 a1 到 a 7 a_7 a7 的和,那么可以通过 c 4 + c 6 + c 7 c_4+c_6+c_7 c4+c6+c7 得到。观察这三个元素下标的规律,我们可以发现:
6 = 7 − l o w b i t ( 7 ) , 4 = 6 − l o w b i t ( 6 ) 6=7-lowbit(7),4=6-lowbit(6) 6=7−lowbit(7),4=6−lowbit(6)
可得代码:
int count(int x) {
int res = 0;
for (int i = x;i >= 1; i -= lowbit(i)) res += c[i];
return res;
}
如果要求
∑
i
=
l
r
a
i
\sum_{i=l}^r a_i
∑i=lrai,利用前缀和,
∑
i
=
1
r
a
i
−
∑
i
=
1
l
−
1
a
i
\sum_{i=1}^ra_i-\sum_{i=1}^{l-1}a_i
∑i=1rai−∑i=1l−1ai , count(r) - count(l)
即可。
4 离散化
存储大数的方法,例如:
1,10000000,99999999,12345,64678
将它们排序得到:
1,12345,64678,10000000,99999999
对他们标号,依次对应:
1,2,3,4,5
可以利用 map 来保存编号。
代码如下:
for (int i = 1; i <= n; i++)
b[i] = a[i];
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i++)
mp[b[i]] = i;