树状数组及各种操作
同样是先挖坑再慢慢填
首先要知道一个贯穿树状数组始终的操作: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
函数没有变化。