树状数组(BIT)初学
BIT(Binary Indexed Tree,BIT) 树状数组。树状数组是一类怎样的数据结构呢?我们知道,树状数组是用来解决动态连续和的查询问题而诞生的。
数据结构就是说,给你n个元素的数组a[1],a[2],a[3]....a[n](下标要从1开始)然后,支持以下两种操作:
1.Add(x,y)就是说对下标为x的a[x]加d。模板默认为update(x,y)
2.Query(L,R)就是计算a[L]+a[L+1]+...+a[R]。模板默认为read(x)求出1-x的和,然后read(y)求出1-y的和,然后再用read(y)-read(x-1)得到[x,y]的和
所以说,BIT是一种动态的连续和查询问题。
在学习BIT之前,我们必须对于位运算有一定的认识和了解,我们知道,对于负数来说,在计算机中是以其补码的形式存放的,而补码就是在其原码的基础上,符号位不能发生变化,其余各位取反后加一得到的结果。
看看这样的运算,比如说7的二进制表示:00000111,那么-7的二进制表示就是:1111001,那么 00000111&11110001的结果是00000001,所以
这样的运算帮我们找到了7 的二进制表示中,最后一个1的位置,有了这个概念。我们如果进行j-=j&(-j)的运算,就知道了这是在把111->110->100->000的过程了。
说了这么多了,那就来说说树状数组到底是怎么实现的呢?用tree[]. tree[i]表示的是[i-(i&(-i))+1,i]这个 区间内a数组元素的和 (为什么会是这个,下文会有解释)
比如我们for( int i = 1;i <= n;i++ )a[i]=i;
想要求出a[1]~a[15]的元素的值。我们知道15的二进制表示是(1111)2
tree[15] = sum[15,15];
tree[14] = sum[13,14];
tree[12] = sum[9,12];
tree[8] = sum[1,8];
-> sum[1,15] = tree[1,8]+tree[9,12]+tree[13,14]+tree[15,15];
为什么会是这样的结果,我们发现,对于结点i来说,如果他是左子结点的话,那么父亲的节点编号就是i+i&(-i)。
如果i是右子结点的话,那么父亲节点的编号就是i-i&(-i);
不难证明,这两个操作的时间复杂度都是O(nlogn),预处理的时候,先把A数组和C数组清空,然后执行n次update()操作,总的时间复杂度是nlogn。
-read( int pos )求出sum[1,pos]。
-update( int pos,int v ) 把a[pos]加v
需要注意的是:更新点然后查询区间,下标必须从1开始。
如果要是计算sum[l,r]的值,那我们就用read(l)-read(r-1)来实现
下面来介绍read的两种实现方式.
第一种,就是用while写的
int read ( int pos ) { int ans = 0; while ( pos>0 ) { ans+=tree[pos]; pos-=pos&(-pos); } return ans; }
第二种,使用for写的
int read ( int pos ) { int ans = 0; for ( int j = pos;j;j-=j&(-j) ) { ans+=tree[j]; } }
再来就是update( int pos,int val )的写法了
第一种,while写法
void update( int pos,int val ) { while ( pos <= n ) { tree[pos]+=val; pos+=pos&(-pos); } }
第二种,for写法
void update( int pos,int val ) { for ( int j = pos;j <= n;j+=j&(-j) ) { tree[j]+=val; } }
根据自己的习惯来写吧,其实都不还可以。
数据结构学习路线:
树状数组->堆->线段树->平衡树->可并堆->持久化线段树->树套树(->仙人掌)