[模板][数据结构] 树状数组
树状数组及各种操作
同样是先挖坑再慢慢填
首先要知道一个贯穿树状数组始终的操作:lowbit
int lowbit(int x){
return x & (-x);
}
这个函数的意思是:返回 \(x\) 的二进制表达中第一个 \(1\) 的位置(以 \(2^i\) 的形式表示)
如何做到的:因为整数是以补码表示的,
举个例子:
\((2)_{dec} = (00000010)_{bin}\)
$ (-2)_{dec} = (11111110)_{bin} $
所以 lowbit(2) = 2
。
而这个 lowbit 值恰好表示了树状数组上一个点所包含的区间
以树状数组为c[]
,原数组为a[]
为例:
c[1] = a[1]
c[2] = a[1] + a[2]
c[3] = a[3]
c[4] = a[1] + a[2] + a[3] + a[4]
以此类推。
这样我们就有了树状数组的基本结构。
单点修改 + 区间求和
还是举例子:
操作:将 a[1]
加上 \(5\)
那么管理它的每一个位置都要加上 \(5\)。
即 c[1]
、c[2]
、c[4]
……… 都要加上 \(5\)。
这时我们会发现每个要更新的数组恰恰为前一个数组的位置加上lowbit值。
于是有以下代码:
void add(int val, int pos){
while(pos <= n){
c[pos] += val;
pos += lowbit(pos);
}
} // 单点加
至于单点修改为某值仅需要略微修改,不再赘述。
而区间求和自然就不难了,只需逆向进行上面的操作(然后作个差):
int getsum(int pos){
int ans = 0;
while(pos > 0){
ans += c[pos];
pos -= lowbit(pos);
}
return ans;
} // 求 1 ~ pos 之和
区间修改 + 单点求和
(这个其实不常用,一般用区间求和版本的比较多)
将树状数组维护的数组建为差分数组,这样修改区间值的时候实际只需要在差分数组的 \(l\) 和 \(r+1\) 位置操作。
注意此时的 getsum
返回的是询问的 pos
的值。
void rangeAdd(int l, int r, int val){
add(l, val); add(r+1, -val);
return;
}
LL getsum(int pos){
LL ans = 0;
while(pos > 0){
ans += d[pos];
pos -= lowbit(pos);
}
return ans;
}
此时的 add
函数没有变化。