声明:本篇文章图片非原创
目录
简介
树状数组或二叉索引树(英语:Binary Indexed Tree),又以其发明者命名为Fenwick树,最早由Peter M. Fenwick于1994年以A New Data Structure for Cumulative Frequency Tables为题发表在SOFTWARE PRACTICE AND EXPERIENCE。其初衷是解决数据压缩里的累积频率(Cumulative Frequency)的计算问题,现多用于高效计算数列的前缀和, 区间和。 ——源自百度百科
百度百科太过于学术化,看不懂?怎么办?总的来说,树状数组是一种支持单点修改,区间查询的一种代码量小的数据结构。
那么树状数组和线段树有什么区别呢?
- 线段树的码量很明显比树状数组多
- 线段树可以进行区间修改和区间查询,然而树状数组是线段树的低级版本,可以进行区间查询,但只能一次性改一个点,当然,经过改造后的树状数组可以进行区间修改,我们今天先讲树状数组的低级版本。
如上图,这就是树状数组的形式,仔细看一看,你会发现树状数组是不规则的叉树。下图是线段树的样子
两者还是有很多地方是不一样的。
lowbit函数
要想学会树状数组,我们必须要理解一个函数:lowbit()
lowbit()就是求一个数与上他的相反数,简单来说,就是x&(-x)
想要算lowbit(x)很简单,举个栗子:
假设一个数 x=5,5的二进制编码为0101。根据我们的二进制原理,则-x也就是1011,所以x&(-x)=1,如果你再举几个例子,细心地你就会发现,x的二进制编码的第一个1的位置就是lowbit(x)的值?
下面给出lowbit的函数模板
//version 1
int lowbit(int x){
return x&(-x);
}
//version 2
#define lowbit(x) x&(-x)
结构分析
先放一张图片
看了这一张图片,我们会发现,每一层的下表的二进制编码末尾的0的个数都是相同的,而且是从下至上一次递增的。
那么原数组前4项的和t[4]=t[2]+t[3]+a[4]=t[1]+a[2]+t[3]+a[4]=a[1]+a[2]+a[3]+a[4]
看了这一张图片之后,你是不是发现了更多的奥秘了呢? 树状数组中节点x的父节点为x+lowbit(x),例如t[2]的父节点为t[4]=t[2+lowbit(2)].
当你理解了这些之后,就可以尝试一下编写代码了.
单点修改,区间查询
我们消化了上面的内容之后,会发现,编写树状数组的代码会变得尤为简单.在这里,我们先来看一下如何在树状数组里进行单点修改,区间查询.
如果我们将a数组进行+k操作,那么它的父亲节点都要加上一个k. 举个栗子:如果我们要将a[1]数组进行+k操作,那么如上图t[1],t[2],t[4],t[8]都要加上一个k(因为我们是用差分建的数,所以更改单点的值时也要牵扯到它的祖宗).
此时,我们就要运用到我们前面学的lowbit函数了!下面是单点修改的代码
int one_change(int x,int k){
for(int i=x;i<=n;i+=lowb