树状数组

树状数组

  树状数组的修改和求和都是O(logn),效率非常高。

  树状数组——lowbit(x)例如21二进制是10101,1所在的位置是0,2,4,可以分解成2^4 + 2 ^ 2 + ^ 0。进一步的[1,x]可以分解成O(logx)个小区间:

  1.长度为2^4的小区间[1,2^4]

  2.长度为2^2的小区间[2^4 + 1,2^4+2^2]

  3.长度为2^0的小区间[2^4+2^2+1,2^4+2^2+2^0]

  树状数组如图

  C[1] = A[1];

  C[2] = A[1] + A[2];

  C[3] = A[3];

  C[4] = A[1] + A[2]+A[3] + A[4];

  C[5] = A[5];

  C[6] = A[6] + A[5];

  C[7] = A[7];

  C[8]=A[1] + A[2]+A[3] + A[4]+A[5] + A[6]+A[7] + A[8]

  树状数组满足以下性质:

  1.每个内部结点C[x]保存以它为根的子树所有叶节点。

  2.每个内部结点C[x]的子结点个数等于lowbit(x)的大小。

  3.除树根外,每个内部结点c[x]的父结点是c[x + lowbit(x)]

  4.树的深度为O(logn)。

  一、求lowbit(n)

  lowbit(n)就是非负整数n在二进制表示下最低位的1以及它后边的0构成的数值。

  {补码表达式~n = -1-n }因此lowbit(n) = n&(~n+1) = n&(-n)。

1 int lowbit(n)
2 {
3     return n&(-n);
4 }

 

  二、对某个元素进行加法操作

  任意一个结点的祖先至多只有logN个,我们逐一对他们的数组C值进行更新即可。下面的代码在O(logN)时间内执行单点增加操作。

1 void update(int x,int y)
2 {
3     for(i = 1;i <= N;x += x & (-x))
4     {
5         c[x] = c[x] + y;
6     }
7 }

  三、查询前缀和

  如本篇开头所写,分成O(logn)个数组,每个数组的区间和都已保存在数组C中,下面的代码在O(logn)的时间内查询前缀和。

 

 1 int sum(int x)
 2 {
 3     int ans = 0;
 4     while(x > 0)
 5     {
 6         ans = ans + c[x];
 7         x = x - (x & (-x));
 8         return ans;
 9     }
10 }

 

  四、统计A[x]……A[y]的值。

  调用以上的sum的操作:sum(y) - sum(x - 1)

  五、扩展(多维树状数组)

  复杂度变成O(logn)^m

  有一个N*M的二维数组,树状数组是c,那么单点修改操作为:

 1 int update(int x,int y,int z)//将(x,y)的值加上z 
 2 {
 3     int i= x;
 4     while(i <= n)
 5     {
 6         int j = y;
 7         while(j <= m)
 8         {
 9             c[i][j] += z;
10             j += lowbit(j); 
11          } 
12          i += lowbit(i);
13      } 
14 }
 1 int sum(int x,int y)
 2 {
 3     int res = 0,i = x;
 4     while(i > 0)
 5     {
 6         int j = y;
 7         while(j > 0)
 8         {
 9             res = res + c[i][j];
10             j -= lowbit(j);
11         }
12         i -= lowbit(i);
13     }
14     return res;
15 }

  六、注意事项

  树状数组的下标不能是0,否则lowbit(0) = 0 ,会陷入死循环中。

 

 

 

原文地址:https://www.cnblogs.com/rax-/p/9424814.html