树状数组:区间查询 + 单点增加
概念:
一个基于二进制思想的数据结构,这可以能够使求前缀和和修改的时间复杂度控制在nlogm。
原理:
把一个[1,x]的区间划分为log(x)个小区间,并用tr[x]保存每个区间[x - lowbit(x) + 1,x]中所有数的和
用途:总的来说,就是求区间和的问题
可以在O(log N)的时间内进行 区间查询 和 单点增加
满足的性质:
- 每个内部节点 tr[x] 保存以它为根节点的子树中所有叶节点的和
- 每个内部节点 tr[x] 的子节点个数等于lowbit(x)的位数
- 除树根外,每个内部节点 tr[x] 的父节点是 tr[x + lowbit(x)]
- 树的深度为O(log N)
如果N不是2的整次幂,那么数组数组就是具有同样性质的森林结构
在附一张图解:(扣来的)
实现:
1. 查询前缀和
int ask(int x)
{
int ans = 0;
for(int i = x; i; i -= lowbit(x)) ans += tr[x]; //
return ans;
}
2. 单点增加(给序列中的一个数a[x]加上y)
void add(int x, int y)
{
for(int i = x; x <= n; i += lowbit(x)) tr[x] += y;
}
lowbit:能够找到一个数在二进制表现下最后一位1,
lowbit的实现:
int lowbit(int x)
{
return x & -x;
}
树状数组的扩展1:区间增加 + 单点查询
用差分来维护树状数组 可以巧妙的把区间增加 和 单点查询 变为 区间查询 和 单点增加
例题:AcWing 242. 一个简单的整数问题
树状数组的扩展2:区间增加 + 区间查询
首先还是用差分来建立树状数组b[ ],
区间增加
因为是差分,所以只要在b[l] + d, b[r + 1] - d即可
区间查询
∑
i
=
0
n
x
i
\sum_{i = 0}^{n} x_i
i=0∑nxi
假设求1 ~ n的区间所有的和:
∑
i
=
1
n
a
i
=
∑
i
=
1
n
∑
j
=
1
i
b
i
\sum_{i_ = 1}^{n} a_i = \sum_{i = 1}^{n} \sum_{j = 1}^{i} b_i
i=1∑nai=i=1∑nj=1∑ibi
图解:
有上图可知sum = (
b
1
b_{1}
b1 +
b
2
b_{2}
b2 + … +
b
n
b_{n}
bn) * (n + 1) - (
b
1
b_{1}
b1 + 2 *
b
2
b_{2}
b2 + 3 *
b
3
b_{3}
b3 + … + n *
b
n
b_{n}
bn)
所以我们可以建2个树状数组来维护:
tr1[]来维护b[i]的前缀和
tr2[]来维护b[i] * i的前缀和
这样就可以实现区间增加和区间查询了
例题:AcWing 243. 一个简单的整数问题2