Binary Indexed Tree, 树状数组是一种支持 单点修改 和 区间查询 的,代码量小的数据结构。
用于解决动态前缀和的[[数据结构]],涉及区间问题
事实上,树状数组能解决的问题是[[线段树]]能解决的问题的子集:树状数组能做的,线段树一定能做;线段树能做的,树状数组不一定可以。然而,树状数组的代码要远比线段树短,时间效率常数也更小
我们总能将一段前缀[1, n]拆成 不多于logn段区间,使得这 logn 段区间的信息是 已知的。
区间查询
查询复杂度 O(logn) (每次抹掉二进制最后一个1)
查询这个节点管辖的区间
设节点编号为x,那么这个节点管辖的区间为 2^k 个元素,(其中k为二进制末尾0的个数)
因为这个区间的最后一个元素必然为Ax
事实上,将有关[l, r]的区间询问转化为[1, r] 和 [1, l-1] 的前缀询问再差分,在竞赛中是一个非常常用的技巧。
int getsum(int x) { // a[1]..a[x]的和
int ans = 0;
while (x > 0) {
ans = ans + c[x];
x = x - lowbit(x);
}
return ans;
}
单点修改
修改相当于查询,是一个逆的过程
我们的目标是快速正确地维护 c 数组。为保证效率,我们只需遍历并修改管辖了a[x]的所有c[y],因为其他的 显然没有发生变化。
void add(int x, int k) {
while (x <= n) { // 不能越界
c[x] = c[x] + k;
x = x + lowbit(x); // 往上(后)跳
}
}
lowbit函数
返回某个数二进制末尾的1的位置
注:负数在二进制使用补码的方式储存,即反转bit然后+1
lowbit(int x) {
return x & (-x);
}
建树
也就是根据最开始给出的序列,将树状数组建出来(c 全部预s处理好)。
一般可以直接转化为 n 次单点修改,时间复杂度 (n log n)。
比如给定序列 a = (5, 1, 4) 要求建树,直接看作对 a[1] 单点加 5,对 a[2] 单点加 1,对 a[3] 单点加 4 即可。 (注意这里下标从1 开始)
![[c20de385208081bade1c094229f692f.jpg]]
区间修改+单点查询
- 新建数组a和d,a是数值,d是树状数组维护的 差分数组
- 设l,r,v。每次进行区间修改的时候,add(l, v),add(r+1, -v) 相当于在lr区间中施加了v的影响
- (在l处开始影响,在r+1处结束影响)
- 要查询修改过的点x:a[x] + query(x);,意思是原值加上v的"影响"
- 差分数组做一次前缀和就得到了总影响
相当于普通差分数组,树状数组维护后的差分数组单点查询的时间复杂度是 O(logn)
而普通差分数组修改后再查询的时间复杂度是 O(n)
#区间问题