树状数组

看到树状数组后觉得这个数据结构很优美,比较有意思,虽然很多时候线段树能做,但树状数组内存消耗更小,思想也很有意思,就想记录一下

 

 看上去是比较漂亮的,A[] 是序列的实际数值, C[] 记录的是某一段A[]的和,例如 C[4]就是 sun(1--4)。

先介绍个很关键的函数:int lowbit(int x){return x&(-x);} 这个可以返回二进制数 x 中最低位的 1 还是 1 ,其余都为 0 的数,比如对于 1000(8的二进制) 1000=100+lowbit(100)=110+lowbit(110)=111+lowbit(111);

其实 C[i] 有很多性质:

1,将 i 化为二进制,比如 i = 6 = 110 那么 lowbit(6) = 2 ,代表 C[6] 记录了 2 个数的和, lowbit(7)=1 ,所以 C[7] 记录的 1 个数的和,所以查询时 i-lowbit(i) 会到 C[i] 恰好没覆盖到的地方

2,C[i]的父节点是 C[i+lowbit(i)],也就是说,i+lowbit(i) 是记录了 i 记录的前缀和的,所以更新时要不断向父节点更新。

看懂了这些再看看代码,很容易理解了

 求区间和O(log n),便于单点更新,区间查询:

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <string.h>
 4 
 5 using namespace std;
 6 #define MAXN 1000
 7 
 8 int n;
 9 int A[MAXN];
10 int C[MAXN];
11 
12 int lowbit(int x)
13 {
14     return x&(-x);
15 }
16 
17 void update(int x,int add) //A[x]增减
18 {
19     while (x<=n)
20     {
21         C[x]+=add;
22         x+=lowbit(x);
23     }
24 }
25 
26 void new_tree(int n)   //建树,直接利用update
27 {
28     memset(C,0,sizeof(C));
29     for (int i=1;i<=n;i++)
30         update(i,A[i]);
31 }
32 
33 int getsum(int x)//[1--x]的和
34 {
35     int sum=0;
36     while(x>0)
37     {
38         sum+=C[x];
39         x -= lowbit(x); //性质1
40     }
41     return sum;
42 }
43 
44 int main()
45 {
46     scanf("%d",&n);
47     for (int i=1;i<=n;i++)
48         scanf("%d",&A[i]);
49     new_tree(n);
50 
51     int l,r;
52     scanf("%d %d",&l,&r);
53     printf("%d\n",getsum(r)-getsum(l-1)); //l--r 的和
54 
55     return 0;
56 }
View Code

 虽然用前缀和求区间和是O(1),但是不便更新

 

树状数组还可以很方便的做区间更新,单点查询,更新时 update(L,v),update(R+1,-v) ,查询为 get_sum(i), 即可,还是因为树状数组保存的是前缀和的原因

 

 还可以升级到多维的,很厉害

posted @ 2017-04-01 20:12  happy_codes  阅读(137)  评论(0编辑  收藏  举报