ACM___________________________

______________白白の屋
posts - 182, comments - 102, trackbacks - 0, articles - 0

树状数组 小结

Posted on 2010-08-25 20:19 MiYu 阅读(749) 评论(0)  编辑 收藏 引用 所属分类: ACM_资料ACM ( 树状数组 )

今天学习了 树状数组, 自己小结下 :

 

简单介绍下 树状数组 :

        树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组a[1...n],那么查询a[1] + …… + a[i] 的时间是log级别的,而且是一个在线的数据结构,支持随时修改某个元素的值,复杂度也为log级别。

 

来观察一下这个图:

 


 

令这棵树的结点编号为C1C2……Cn。令每个结点的值为这棵树的值的总和,那么容易发现:

C1 = A1

C2 = A1 + A2

C3 = A3

C4 = A1 + A2 + A3 + A4

C5 = A5

C6 = A5 + A6

C7 = A7

C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

……

C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16

 

这里有一个有趣的性质,下午推了一下发现:

设节点编号为x,那么这个节点管辖的区间为2^k(其中kx二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,所以很明显:

Cn = A(n – 2^k + 1) + …… + An

算这个2^k有一个快捷的办法,定义一个函数如下即可:

int lowbit(int x){

return x & (x ^ (x – 1));

}

 

当想要查询一个SUM(n)时,可以依据如下算法即可:

step1: 令sum = 0,转第二步;

step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步;

step3:  n = n – lowbit(n),转第二步。

 

可以看出,这个算法就是将这一个个区间的和全部加起来,为什么是效率是log(n)的呢?以下给出证明:

n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)1,所以查询效率是log(n)的。

 

那么修改呢,修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。所以修改算法如下(给某个结点i加上x):

step1: i > n时,算法结束,否则转第二步;

step2: Ci = Ci + x i = i + lowbit(i)转第一步。

 

i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。

//修改过程必须满足减法规则!

 

所以整个程序如下:

const int MAX = 50000;

#define lowbit(x) ((x)&(-x))

int com[ MAX + 1 ],N,T;

void modify ( int pos, int val ){

     while ( pos <= N ){

            com[pos] += val;

            pos = pos + lowbit(pos);  

     } 

}

int quy ( int x ){

    int sum = 0;

    while ( x > 0 ){

           sum = sum + com[x];

           x = x - lowbit(x);

    } 

    return sum;

}

初始化 :     for ( int i = 1; i <= N; ++ i ){

                       scanf ( "%d",&x );

                       modify ( i, x ); 

                 } 

------------------------------------------------------------------------------------

为什么要用树状数组 ?

 

原因如下: 

 

|  1. 首先我们得知道一个问题,那就是线段树得作用并

 

不只是用来存储线段的,也可以存储点的值等等.  


|  2. 对于静态的线段树,空间上需要的数组有:当前结点

 

的数据值,左儿子编号,右儿子编号.至少这么三个数组

 

    3. | 而在时间上虽然是NlogN的复杂度,但是系数很大.|

 

  4. 实现起来的时候编程复杂度大,空间复杂度大,时间

 

效率也不是很理想

 

 

 

 

 

 所以这个时候树状数组成了一个很好的选择.

 

先看一个例题:

 

数列操作:

 

    给定一个初始值都为0的序列,动态地修改一些 

 

位置上的数字,加上一个数,减去一个数,或者乘上

 

一个数,然后动态地提出问题,问题的形式是求出

 

一段数字的和. 

 

 

如果直接用朴素方法做的话,修改的复杂度是O(1), 

 

询问的复杂度是O(N),M次询问的复杂度是M*N.

 

M,N的范围可以有100000以上!!!!

 

 

 

 有没有更好的方法???

 

 

 

 呵呵, 肯定有啦, 就是我要说的树状数组!!!  

 

具体的树状数组的解释请看上面,  那么这个2k怎么求? 是怎么

 

来的?  

 

K的计算可以这样:

 

|2k=x and (x & (x-1))

 

|6为例

 

|                (6)10=(0110)2

 

|xor    6-1=(5)10=(0101)2

 

|                         (0011)2

 

|and          (6)10=(0110)2

 

|                         (0010)2

 

所以: 

 

由数字的机器码可以更简单的优化成:  x & (-x)

 

对于上面那一题,每次修改与询问都是对C数组做处理.

 

空间复杂度有3N降为N,时间效率也有所提高.编程复杂

 

度更是降了不少.

 

 

 

 

 

 

 


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理