二叉索引树,即树状数组,被某神犇称之为是最漂亮的数据结构,所以蒟蒻北篱也去学习了一下传说中的树状数组。
限于蒟蒻北篱的语言表达能力太差(其实是懒),于是引用了度娘的一段对树状数组的解释
树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
树状数组可以O(logn)的完成单点修改,单点查询和区间查询等操作,其实就是动态维护前缀和的过程,其中的所有操作都是以lowbit(x)操作为核心的。
lowbit
先来说一下lowbit(x)操作,lowbit(x)操作其实就是求x的二进制最低位(最低位代表的数字而不是位数),求x的lowbit也很简单,代码如下:
1 inline int lowbit(int x) { 2 return x&(-x); 3 }
其实就是利用了补码,证明很简单,这里略过。如果你不想写函数也可以宏定义:
1 #define lowbit(x) ((x)&(-(x)))
加多层括号是为了防止优先级错误,不必要时完全可以不加。
存储结构
树状数组看名字就知道只是一个数组,没什么好说的,但是,每个数组元素存储的不是原数组中的内容(这不是废话吗),而是原数组中(x-lowbit(x),x]内所有元素的和,而这就是我们查询时间为O(logn)的原因。
单点修改
如果要把数组中下标为x的元素加上t,那么学过数组的人肯定都会写:
1 inline void change(int x,int t) { 2 c[x]+=t; 3 }
这样就行了吗?不行。
我们在说存储结构的时候就说过了,树状数组中下标为元素的值等于原数组中(x-lowbit(x),x]内所有元素的和,所以我们修改树状数组的其他元素,我们假设树状数组有n个元素,于是代码如下:
1 inline void change(int x,int t) { 2 for(;x<=n;x+=lowbit(x)) { 3 c[x]+=t; 4 } 5 }
这样才是没有问题的修改操作。如果看懂了上面的存储原理想要理解的话也不难,理解不了就多看几遍图就懂了。
有没有发现我们并没有说建树的代码?那是因为直接把每一个点调用一次change操作就好了。
前缀和查询
我们利用树状数组可以做到O(logn)的前缀和查询,和单点修改类似的原理,这里不再多说,看代码就能懂的(笑
1 inline int sum(int x) { 2 int s=0; 3 for(;x;x-=lowbit(x)) { 4 s+=c[x]; 5 } 6 return s; 7 }
区间查询
我们可以利用树状数组求[s,t]内所有元素的和,做法很简单,既然我们已经写出前缀和查询的代码,我们现在就要好好利用它,求出sum(t)和sum(s),然后两者相减就能得出答案,所以区间求和的时间也是O(logn)。
1 inline int query(int s,int t) { 2 return sum(t)-sum(s-1); 3 }
树状数组模板
最后丢一个模板好了(溜
1 struct BIT{ 2 private: 3 static const int maxn=5e5+10; 4 int c[maxn],n; 5 public: 6 void init(int n) { 7 memset(c,0,sizeof(c)); 8 this->n=n; 9 } 10 inline void change(int x,int t) { 11 for(;x<=n;x+=lowbit(x)) { 12 c[x]+=t; 13 } 14 } 15 inline int sum(int x) { 16 int s=0; 17 for(;x;x-=lowbit(x)) { 18 s+=c[x]; 19 } 20 return s; 21 } 22 inline int query(int s,int t) { 23 return sum(t)-sum(s-1); 24 } 25 };