树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的全部元素之和,可是每次只能修改一个元素的值;通过简单修改能够在log(n)的复杂度下进行范围修改,可是这时只能查询其中一个元素的值(若是加入多个辅助数组则能够实现区间修改与区间查询)。算法
百度上给出了使人难以理解的概念,其实这个东西我也是琢磨了一天,参考了大量博客的笔记才搞清楚了大体思路和原理,说说心得吧!数组
先看两幅图(网上找的,若是雷同,不要大惊小怪~),下面的说明都是基于这两幅图的,左边的叫A图吧,右边的叫B图:数据结构
是否是很像一颗树?对,这就是为何叫树状数组了~先看A图,a数组就是咱们要维护和查询的数组,可是其实咱们整个过程当中根本用不到a数组,你能够把它看成一个摆设!c数组才是咱们全程关心和操纵的重心。先由图来看看c数组的规则,其中c8 = c4+c6+c7+a8,c6 = c5+a6……先没必要纠结怎么作到的,咱们只要知道c数组的大体规则便可,很容易知道c8表示a1~a8的和,可是c6倒是表示a5~a6的和,为何会产生这样的区别的呢?或者说发明她的人为何这样区别对待呢?答案是,这样会使操做更简单!看到这相信有些人就有些感受了,为何复杂度被lg了呢?能够看到,c8能够看做a1~a8的左半边和+右半边和,而其中左半边和是肯定的c4,右半边其实也是一样的规则把a5~a8一分为二……继续下去都是一分为二直到不能分,能够看看B图。怎么样?是否是有点二分的味道了?对,说白了树状数组就是巧妙的利用了二分,她并不神秘,关键是她的巧妙!函数
她又是怎样作到不断的一分为二呢?说这个以前我先说个叫lowbit的东西,lowbit(k)就是把k的二进制的高位1所有清空,只留下最低位的1,好比10的二进制是1010,则lowbit(k)=lowbit(1010)=0010(2进制),介于这个lowbit在下面会常常用到,这里给一个很是方便的实现方式,比较广泛的方法lowbit(k)=k&-k,这是位运算,咱们知道一个数加一个负号是把这个数的二进制取反+1,如-10的二进制就是-1010=0101+1=0110,而后用1010&0110,答案就是0010了!明白了求解lowbit的方法就能够了,继续下面。介于下面讨论十进制已经没有意义(这个世界原本就是二进制的,人非要主观的构建一个十进制),下面全部的数没有特别说明都看成二进制。this
上面那么多文字说lowbit,还没说它的用处呢,它就是为了联系a数组和c数组的!ck表示从ak开始往左连续求lowbit(k)个数的和,好比c[0110]=a[0110]+a[0101],就是从110开始计算了0010个数的和,由于lowbit(0110)=0010,能够看到其实只有低位的1起做用,由于很显然能够写出c[0010]=a[0010]+a[0001],这就为何咱们任何数都只关心它的lowbit,由于高位不起做用(基于咱们的二分规则它必须如此!),除非除了高位其他位都是0,这时自己就是lowbit。spa
既然关系创建好了,看看如何实现a某一个位置数据跟改的,她不会直接改的(开始就说了,a根本不存在),她每次改其实都要维护c数组应有的性质,由于后面求和要用到。而维护也很简单,好比更改了a[0011],咱们接着要修改c[0011],c[0100],c[1000],这是很容易从图上看出来的,可是你可能会问,他们之间有申明必然联系吗?每次求解总不能总要拿图来看吧?其实从0011——>0100——>1000的变化都是进行“去尾”操做,又是本身造的词--'',我来解释下,就是把尾部应该去掉的1都去掉转而换到更高位的1,记住每次变换都要有一个高位的1产生,因此0100是不能变换到0101的,由于没有新的高位1产生,这个变换过程刚好是能够借助咱们的lowbit进行的,k +=lowbit(k)。code
好吧,如今更新的次序都有了,可能又会产生新的疑问了:为何它非要是这种关系啊?这就要追究到以前咱们说c8能够看做a1~a8的左半边和+右半边和……的内容了,为何c[0011]会影响到c[0100]而不会影响到c[0101],这就是以前说的c[0100]的求解其实是这样分段的区间 c[0001]~c[0001] 和区间c[0011]~c[0011]的和,数字过小,可能这样不太理解,在好比c[0100]会影响c[1000],为何呢?由于c[1000]能够看做0001~0100的和加上0101~1000的和,可是0101位置的数变化并会直接做用于c[1000],由于它的尾部1不能一下在跳两级在产生两次高位1,是经过c[0110]间接影响的,可是,c[0100]却能够跳一级产生一次高位1。orm
可能上面说的你比较绕了,那么此时你只需注意:c的构成性质(实际上是分组性质)决定了c[0011]只会直接影响c[0100],而c[0100]只会直接影响[1000],而下表之间的关系刚好是也必须是k +=lowbit(k)。此时咱们就是写出跟新维护树的代码:htm
1 void add(int k,int num) 2 { 3 while(k<=n) 4 { 5 tree[k]+=num; 6 k+=k&-k; 7 } 8 }
1 int read(int k)//1~k的区间和 2 { 3 int sum=0; 4 while(k) 5 { 6 sum+=tree[k]; 7 k-=k&-k; 8 } 9 return sum; 10 }
下面给出一道模版题吧!blog
POJ 2352
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 45080 | Accepted: 19567 |
Description
Input
Output
Sample Input
5 1 1 5 1 7 1 3 3 5 5
Sample Output
1 2 1 1 0
Hint
Source
就是求每一个小星星左小角的星星的个数。坐标按照Y升序,Y相同X升序的顺序给出
因为y轴已经排好序,能够按照x坐标创建一维树状数组
1 #include <stdio.h> 2 #include <string.h> 3 const int MAXN=32005; 4 const int MINN=15005; 5 int tree[MAXN];//下标为横坐标 6 int level[MINN];//下标为等级数 7 /*int lowerbit(int x) 8 { 9 return x&-x; 10 }*/ 11 void add(int k,int num) 12 { 13 while(k<=MAXN) 14 { 15 tree[k]+=num; 16 k+=k&-k; 17 } 18 } 19 int read(int k)//1~k的区间和 20 { 21 int sum=0; 22 while(k) 23 { 24 sum+=tree[k]; 25 k-=k&-k; 26 } 27 return sum; 28 } 29 int main() 30 { 31 int n,x,y,i; 32 memset(tree,0,sizeof(tree)); 33 memset(level,0,sizeof(level)); 34 while(scanf("%d",&n)!=EOF) 35 { 36 for(i=1;i<=n;i++) 37 { 38 scanf("%d%d",&x,&y); 39 int temp=read(x+1);//加入x+1,是为了不0,X是可能为0的 40 level[temp]++; 41 add(x+1,1); 42 } 43 for(i=0;i<n;i++) 44 printf("%d\n",level[i]); 45 } 46 return 0; 47 }