在学习树状数组前,需要了解lowbit运算.
什么是lowbit运算呢?
lowbit(x)表示返回当前十进制x中,其二进制形式下低位处最先遇到1时,1所表示的值
如 10100 , 低位处最先遇到1在二进制的第三位,则保留后三位, 100 转化位十进制为 4
我们来算出前8的lowbit值
1 -> 1B 0> 1
2 -> 10B -> 10B -> 2
3 -> 11B -> 1B - > 1
4 -> 100B -> 100B -> 4
5 -> 101B -> 1B -> 1
6 -> 110B -> 10B -> 2
7 -> 111B -> 1B -> 1
8 -> 1000B -> 1000B -> 8
所以有如下关系 lowbit(x) = x & -x; 前人智慧真伟大
如 3 & -3 [011]原 & [101]原 -> (001)2 = 1
那么有什么用呢?
如图所示:
数组A存放的是原数据
数组C经过lowbit循环运算得到的域a之间的关系,形成一颗树状的关系,故名思意为树状数组
这张图的规律如下:
C[1] 其lowbit后为 1 对应 a[1]数组
C[2] 其lowbit运算后为 2 对应 a[1] a[2]
。。。。。
所以我们可以知道 在树状数组中,下标为i所对应原数组的关系有:
1)i,为奇数时,对应原数组下标i,只包含一个节点
2)i,为偶数不为2的n次幂时,只包含俩个节点
3)i为2的n次幂时,与当前下标i前的所有原数组有关
树状数组的一般操作有:
1)单点查询(一般数组也可以)
2)单点修改(一般数组也可以)
3)维护序列前缀和/区间修改 (性质最重要) c[x] 维护的是
∑
i
=
x
−
l
o
w
b
i
t
(
x
)
+
1
x
a
[
i
]
\sum_{ i=x-lowbit(x)+1}^x a[i]
∑i=x−lowbit(x)+1xa[i]
4) 区间查询前缀和(性质同样重要)
区间查询和区间修改的时间复杂度均为O(logn)!!! !!! !!!
这就是为为什么树状数组存在的重要原因
维护前缀和代码如下:
void add(int x,int y)
{
//对区间[x,n] 之间的区间+ y
for(; x <=n ; x+=lowbit(x)) c[x] += y;
}
区间查询代码如下;
int ask(int x)
//ask询问的是 区间[1,x]之间的和,注意 c存放的只是n次修改后的增值,不是原值,原值保持不变的
{
int ans = 0;
for(;x;x-=lowbit(x)) ans += c[x];
return ans;
}
常用操作一:
单点增加如a[x]+d
只需要add(x,d)
对区间[L , R] + d
add(L,d) - add(R+1,-d)
查询经过n次操作后a[x] 的值
a[x] = a[x] + ask(x)
同时也可以查询j经过n次操作后 a[L,R]的区间和
我们来看下假设对a1 ,a2, a3, a4, a5, a6 … an之间进行了n次操作
a[1] 增长值:c[1]
a[2] 增长值:c[1] + c[2]
a[3] 增长值:c[1] + c[2] + c[3]
a[4] 增长值:c[1] + c[2] + c[3] + c[4]
…
a[x] 增长值:c[1] + c[2] + c[3] + c[4] +…+c[x]
a[1] ~ a[x] 总增长值: x * c[1] + (x-1) * c[2] + (x-2)* c[3] + (x-3) * c[4] + … 1 *c[x]
推出操作改动后的增值公式为:a[1] ~ a[x] 总增长值 : Σ (x-i+1) * c[i] = (x+1)Σc[i] - Σi * c[i] (1 <= i <= x)
初始值的前缀和
sum[i]表示前 1 ~ i 的前缀和
sum[l,r] = sum[r] - sum[l-1];
n次操作后的sum[l,r]的区间和为
ans[l,r] = sum[l,r] + a[l] ~ a[r] 的总增长值
= ( sum[r] + (r+1) * Σc[i] - Σi * c[i] ,(l<=i <=r))
- (sum[l-1] + (l-1+1) * Σc[i] - Σi * c[i] ,(1 <= i <= l-1 ))
在(l<=i <=r)
ask0( r) = Σc[i]
ask1( r) = Σi * c[i]
在(1 <= i <= l-1 )
ask0(l-1) = Σc[i]
ask1(l-1) = Σi * c[i]
ans[l,r] = (sum[r] + (r+1)*ask0( r) - ask1(l-1) )
- (sum[l-1] + (l-1+1)*ask0(l-1) - ask1(l-1))
如何维护 Σc[i] , Σ (i * c[i]) ( l <= i <= r)
使用add操作
一对 add0(l,d),add0(r+1,-d) 表明 算出了 c[l,r]的改动
一对 add1(l,l*d) add1(r+1,-(r+1)*d) 表明算出了 Σ (i * c[i])[l,r] 上的改动
令 c[0][i] 表示 c[i] 的单点操作
c[1][i] 表示 (i * c[i]) 的单点操作
操作二:可用树状数组求序列 a 的逆序对,前提是a数组内的数据不是特别大
令cnt数组表示a序列中最大数据 到 1之间的出现次数
add(a[x],1) 表示 在a序列前x个数中 a[x] 数据存在次数+1
ask(a[x] - 1) 表示 在 当前序列a[x] 下 1~a[x] - 1 在序列 a下标 x 之前出现的次数
表明序列a位置x之前有多少个数小于a[x]
maxn表示序列a中最大的值 则 ask(maxn) - ask(a[x]) 表示在前x个序列中有多少个大于a[x]的数