树状数组
看到树状数组后觉得这个数据结构很优美,比较有意思,虽然很多时候线段树能做,但树状数组内存消耗更小,思想也很有意思,就想记录一下
看上去是比较漂亮的,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 }
虽然用前缀和求区间和是O(1),但是不便更新
树状数组还可以很方便的做区间更新,单点查询,更新时 update(L,v),update(R+1,-v) ,查询为 get_sum(i), 即可,还是因为树状数组保存的是前缀和的原因
还可以升级到多维的,很厉害