好久都对这个东西有点陌生,看了一下午,终于能说出点缘由来了。。。
从网上找的点资料。。。
一、树状数组是干什么的?
平常我们会遇到一些对数组进行维护查询的操作,比较常见的如,修改某点的值、求某个区间的和,而这两种恰恰是树状数组的强项!当然,数据规模不大的时候,对于修改某点的值是非常容易的,复杂度是O(1),但是对于求一个区间的和就要扫一遍了,复杂度是O(N),如果实时的对数组进行M次修改或求和,最坏的情况下复杂度是O(M*N),当规模增大后这是划不来的!而树状数组干同样的事复杂度却是O(M*lgN),别小看这个lg,很大的数一lg就很小了,这个学过数学的都知道吧,不需要我说了。
二、原理讲解
给定序列(数列)A,我们设一个数组C满足
C[i] = A[i–2^k+ 1] + … + A[i]
其中,k为i在二进制下末尾0的个数,i从1开始算!
则我们称C为树状数组。
下面的问题是,给定i,如何求2^k?
答案很简单:2^k=i&(i^(i-1)) ,也就是i&(-i) 为什么呢?? 请看下面:
整数运算 x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。因为:x &(-x) 就是整数x与其相反数(负号取反)的按位与:1&1=1,0&1 =0, 0&0 =1。具体分析如下:
□ 当x为0时,x&(-x) 即 0 & 0,结果为0;
□ 当x不为0时,x和-x必有一个为正。不失一般性,设x为正。
●当x为奇数时,最后一个比特为1,取反加1没有进位,故x和-x除最后一位外前面的位正好相反,按位与结果为0。最后一位都为1,故结果为 1。
●当x为偶数,且为2的m次方(m>0)时,x的二进制表示中只有一位是1(从右往左的第m+1位),其右边有m位0,左边也都是0(个数由表示 x的字 节数决定),故x取反加1后,从右到左第有m个0,第m+1位及其左边全是1。这样,x& (-x) 得到的就是x。
●当x为偶数,却不为2的m次方的形式时,可以写作x= y * (2^k)。其中,y的最低位为1。实际上就是把x用一个奇数左移k位来表示。这时,x的 二进制 表示最右边有k个0,从右往左第k+1位为1。当对x取反时,最右边的k位0变成1,第k+1位变为0;再加1,最右边的k位就又变成了0,第 k+1位因为进 位的关系变成了1。左边的位因为没有进位,正好和x原来对应的位上的值相反。二者按位与,得到:第k+1位上为1,左边右边都为 0。结果为2^k,即 x中包含的2的最大次方的因子。
总结一下:x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。 比如x=32,其中2的最大次方因子 为 2^5,故x&(-x)结果为32;当x=28,其中2的最大次方因子为4,故x & (-x)结果为4。当x=24,其中2的最大次方因子为8,故 x&(-x)结果为 8。
(结合网址:http://blog.csdn.net/int64ago/article/details/7429868)
三、用途
(1)最基本的数组区间改变、更新、求和。
(2)逆序数
(3)二维线段树求矩阵的更新、求和等(暂未整理)
四、经典题型(详情:http://blog.csdn.net/zhengxu001/article/details/8029790)
源代码模板:
#include<stdio.h> #include<string.h> #include <iostream> using namespace std; #define MAX 100 int a[MAX],c[MAX]; int lowbit(int x){ return x & (-x); } void update(int x,int add){//更新树状数组 x为原数组的下标 add是原数组的值 while(x<MAX){//更新范围,如果数组没全部用到不用全部更新,条件可以设置 c[x]+=add; x+=lowbit(x); } } int get_sum(int x){//求前n项和 int sum=0; while(x!=0){ sum+=c[x]; x-=lowbit(x); } return sum; } int main (){ int a[]={0,1,2,3,4,5,6,7,8,9,10}; for(int i=1;i<=10;i++) update(i,a[i]); for(int i=1;i<=100;i++) cout<<c[i]<<' '; cout<<endl<<endl; for(int i=1;i<=100;i++) cout<<get_sum(i)<<' '; return 0; }