- OI Wiki
- 《算法笔记》
前言
利用大节点来存储小节点的信息。在查询或修改的时候直接利用大节点的信息,节省时间。
事实上,树状数组的代码要比线段树短得多,思维也更清晰,在解决一些单点修改的问题时,树状数组是不二之选。
lowbit运算
形式一
l o w b i t ( x ) = 2 m a x { i │ x m o d 2 i = 0 } lowbit(x) = 2^{max \{{i│x\mod 2^i=0\} }} lowbit(x)=2max{i│xmod2i=0}
能整除x的最大2的幂次
形式二
l o w b i t ( x ) = x & ( − x ) lowbit(x) = x \&(-x) lowbit(x)=x&(−x)
补码储存方式: − x -x −x相当于把 x x x二进制每一位取反,再+1,等价于把 x x x二进制最右边1的左边的每一位都取反。
那么KaTeX parse error: Expected '}', got '&' at position 9: x\text{ &̲ }(-x)就是x二进制最右边的1和右边的0。
树状数组
基本知识
假设我们大小节点之间的关系是求和。对于原数组 a,我们用新的树状数组 t 来辅助。
t i = a i , i = 1 , 3 , 5 , 7 t_i=a_i, i=1,3,5,7 ti=ai,i=1,3,5,7
t 2 = a 1 + a 2 , t 6 = a 5 + a 6 t_2=a_1+a_2, t_6=a_5+a_6 t2=a1+a2,t6=a5+a6
t 4 = a 1 + a 2 + a 3 + a 4 t_4=a_1+a_2+a_3+a_4 t4=a1+a2+a3+a4
t 8 = ∑ i = 1 8 a i t_8=\sum^8_{i=1} a_i t8=∑i=18ai
如果要查 a 1 → a 5 a_1→a_5 a1→a5的和,如果树状数组存储的是和,直接用 t 5 + t 4 t_5+t_4 t5+t4 即可。查 a 1 → a 7 a_1→a_7 a1→a7 的和,直接 t 7 + t 6 + t 4 t_7+t_6+t_4 t7+t6+t4 即可
那么我们主要要解决两个问题:
- 怎么知道某个点由哪几个点组成?
- 怎么修改和查询?
怎么知道某个点由哪几个点组成
对于编号为 x x x的某点,由 l o w b i t ( x ) lowbit(x) lowbit(x)个点组成
-
以 t [ 6 ] t[6] t[6]为例, l o w b i t ( 6 ) = 2 lowbit(6) = 2 lowbit(6)=2,有两个点组成
因为 6 ( 10 ) = 11 0 ( 2 ) 6_{(10)} = 110_{(2)} 6(10)=110(2),按照lowbit定义 11 0 ( 2 ) 110_{(2)} 110(2)最右边的1和右边的0为 1 0 ( 2 ) 10_{(2)} 10(2),对应十进制为2。
int lowbit(int x) {
return x & -x;
}
单点修改+单点查询
复杂度 O ( l o g n ) O(logn) O(logn)
单点修改:修改一个元素 x 后对哪些数产生了影响?可以由图直观得出产生影响的范围是 x 通过不断加 lowbit(x) 能得到的值。
// 单点修改(第x个整数加v)
void update(int x, int v) {
for(int i = x; i<= n; i += lowbit(i))
a[i] += v;
}
单点查询:每次让 x 减去 lowbit(x)
// 单点查询
int getsum(int x) { // a[1]……a[x]的和
int sum = 0;
for(int i = x; i> 0; i -= lowbit(i)){ // 注意是>0
sum += a[i];
}
return sum;
}