树状数组初步_ZERO

原博客:树状数组



                                  

                 

1 一维树状数组

  1 什么是树状数组
       树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组A[1..n],那么查询A[1]+…+A[n]的时,间是log级别的,而且是一个在线的数据结构。

  2 树状数组作用
       我们经常会遇到动态连续和查询问题,给定n个元素A[1~N],让我们求sum[L,R] = A[L]+…+A[R],或者更改A[i]的值。

       假设数据很小的时候,那么我们利用暴力就可以搞定,这个时候更改A[i]的复杂度为O(1),但是求和的复杂度为O(n),如果有m次求和就是O(n*m),但是m很大的时候这个方法显然是不能够满足效率的要求。这个时候我们引入树状数组,树状数组的求和和更新都是O(logN),所以大大的降低了复杂度。


  3 具体分析

     1 建立树状数组就是先把A[] 和 C[]清空,然后假设有n个数那么就是做n次的update()操作就是建立树状数组,所以总的时间复杂度为O(nlogn)。

     2 设原数组为A[1..N],树状数组为c[1..N],其中c[k] = A[k-(2^t)+1] + … + A[k]。比如c[6] = A[5] + A[6]。

        假设 A为被计数数组,C为树状数组(计数)

        0000 0001:C1 = A1
        0000 0010:C2 = A1 + A2
        0000 0011:C3 = A3
        0000 0100:C4 = A1 + A2 + A3 + A4
        0000 0101:C5 = A5
        0000 0110:C6 = A5 + A6
        0000 0111:C7 = A7
        0000 1000:C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
        …
        0001 0000:C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8+ A9 + A10 + A11 + A12 + A13 + A14 + A15+ A16


     3 也就是说,把k表示成二进制1***10000,那么c[k]就是A[1***00001] + A[1***00010] + … + A[1***10000] 这一段数的和。


     4 设一个函数lowbit(k)为取得k的最低非零位,容易发现,根据上面的表示方法,从A[1]到A[k]的所有数的总和即为
        sum[k] = c[k] + c[k-lowestbit(k)] + c[k-lowestbit(k)-lowestbit(k-lowestbit(k))] + … 于是可以在logk的时间内求出sum[k]。


     5 当数组中某元素发生变化时,需要改动的c值是c[k],c[k+lowestbit(k)], c[k+lowestbit(k)+lowestbit(k+lowestbit(k))] … 这个复杂度是logN (N为最大范围)


     6 如果题目要求sum[L , R] = sum[R]-sum[L-1]
        sum[L-1] = A[1]+A[2]+…+A[L-1]
        sum[R] = A[1]+A[2]+…+A[L]+…+A[R]
        sum[R]-sum[L-1] = A[L]+A[L+2]+…+A[R]


     7 树状数组的下标严格从1开始,所以如果出现0的情况要注意加1.(因为lowbit(0)是0所以如果出现为0的时候会进入无限循环中) , 树状数组中的每个元素至少含有它本身的一个值。



2 二维树状数组

   1 二维树状数组说白了就是每一维都是树状数组

      问题:一个由数字构成的大矩阵,能进行两种操作
                1 对矩阵里的某个数加上一个整数(可正可负)
                2 查询某个子矩阵里所有数字的和,要求对每次查询,输出结果。

   2 一维树状数组很容易扩展到二维,在二维情况下:数组A[][]的树状数组定义为:
     C[x][y] = ∑ a[i][j], 其中,x-lowbit(x) + 1 <= i <= x , y-lowbit(y) + 1 <= j <= y.

   3 例:举个例子来看看C[][]的组成。
             设原始二维数组为:
          A[][]={{a11,a12,a13,a14,a15,a16,a17,a18,a19},
                       {a21,a22,a23,a24,a25,a26,a27,a28,a29},
                       {a31,a32,a33,a34,a35,a36,a37,a38,a39},
                       {a41,a42,a43,a44,a45,a46,a47,a48,a49}};
             那么它对应的二维树状数组C[][]呢?

      记:
             B[1]={a11,a11+a12,a13,a11+a12+a13+a14,a15,a15+a16,…} 这是第一行的一维树状数组
             B[2]={a21,a21+a22,a23,a21+a22+a23+a24,a25,a25+a26,…} 这是第二行的一维树状数组
             B[3]={a31,a31+a32,a33,a31+a32+a33+a34,a35,a35+a36,…} 这是第三行的一维树状数组
             B[4]={a41,a41+a42,a43,a41+a42+a43+a44,a45,a45+a46,…} 这是第四行的一维树状数组

      那么:
             C[1][1] = a11 , C[1][2] = a11+a12 , C[1][3] = a13 , C[1][4] = a11 + a12 + a13 + a14 , c[1][5]=a15.这是A[][]第一行的一维树状数组

              C[2][1] = a11 + a21 , C[2][2] = a11 + a12 + a21 + a22 , C[2][3] = a13 + a23 , C[2][4] = a11 + a12 + a13 + a14 + a21 + a22 + a23 + a24 这是A[][]数组第一行与第二行相加后的树状数组

              C[3][1] = a31 , C[3][2] = a31 + a32 , C[3][3] = a33 , C[3][4] = a31 + a32 + a33 + a34 , C[3][5] = a35 , C[3][6]=a35+a36,…这是A[][]第三行的一维树状数组

              C[4][1] = a11 + a21 + a31 + a41 , C[4][2] = a11 + a12 + a21 + a22 + a31 + a32 + a41 + a42 ,这是A[][]数组第一行+第二行+第三行+第四行后的树状数组



3  树状数组的两类操作

    1 单点更新,区间求和

       1 一维树状数组,单点更新,区间求和

       比如要更新点x ,x点的值加上val即调用add(x , val) , 求区间[1 , x]的和即为getSum(x)

  1.    
  2. int lowbit(int x){  
  3.     return x&(-x);&n bsp; 
  4. }  
  5.   
  6. int getSum(int x){  
  7.     int sum = 0;  
  8.     while(x){  
  9.         sum += treeNum[x];  
  10.         x -= lowbit(x);  
  11.     }  
  12.     return sum;  
  13. }  
  14.   
  15. void add(int x , int val){  
  16.     while(x < MAXN){  
  17.          treeNum[x] += val;  
  18.          x += lowbit(x);  
  19.     }  
  20. }  
 
int lowbit(int x){
    return x&(-x);
}

int getSum(int x){
    int sum = 0;
    while(x){
        sum += treeNum[x];
        x -= lowbit(x);
    }
    return sum;
}

void add(int x , int val){
    while(x < MAXN){
         treeNum[x] += val;
         x += lowbit(x);
    }
}
            2 二维树状数组,单点更新,区间求和

       比如要更新点(x , y) ,(x , y)点的值加上val即调用add(x , y , val) , 求矩形[1 , 1] - [x , y]的和即为getSum(x , y)

           

            如上图求矩形的面积为getSum(x2 , y2)-getSum(x1-1,y2)-getSum(x2,y1-1)+getSum(x1-1 , y1-1)

           

  1. int lowbit(int x){  
  2.     return x&(-x);  
  3. }  
  4.   
  5. int getSum(int x , int y){  
  6.     int sum = 0;  
  7.     for(int i = x ; i > 0 ; i -= lowbit(i))  
  8.        for(int j = y ; j > 0 ; j -= lowbit(j))  
  9.            sum += treeNum[i][j];  
  10.     return sum;  
  11. }  
  12.   
  13. void add(int x , int y , int val){  
  14.     for(int i = x ; i < MAXN ; i += lowbit(i))  
  15.        for(int j = y ; j < MAXN ; j += lowbit(j))  
  16.            treeNum[i][j] += val;  
  17. }  
int lowbit(int x){
    return x&(-x);
}

int getSum(int x , int y){
    int sum = 0;
    for(int i = x ; i > 0 ; i -= lowbit(i))
       for(int j = y ; j > 0 ; j -= lowbit(j))
           sum += treeNum[i][j];
    return sum;
}

void add(int x , int y , int val){
    for(int i = x ; i < MAXN ; i += lowbit(i))
       for(int j = y ; j < MAXN ; j += lowbit(j))
           treeNum[i][j] += val;
}


    2 区间更新,单点求和 

         1 一维树状数组

        更改区间[x , y],区间[x , y]里面的每个数全部加上val , 查询点k的值

        区间[x , y]加上val相当于点x加上val , 点y+1减去val,那么求k点的值就等于[1,k]的和

            

  1. int lowbit(int x){  
  2.     return x&(-x);  
  3. }  
  4.   
  5. int getSum(int x){  
  6.     int sum = 0;  
  7.     while(x){  
  8.         sum += treeNum[x];  
  9.         x -= lowbit(x);  
  10.     }  
  11.     return sum;  
  12. }  
  13.   
  14. void add(int x , int val){  
  15.     while(x < MAXN){  
  16.          treeNum[x] += val;  
  17.          x += lowbit(x);  
  18.     }  
  19. }  
  20.   
  21. void solve(){  
  22.     // 把区间[x , y]每一点加上val  
  23.     add(x , val);  
  24.     add(y+1 , -val);  
  25.     // 计算点k的值  
  26.     int num = getSum(k);  
  27. }  
int lowbit(int x){
    return x&(-x);
}

int getSum(int x){
    int sum = 0;
    while(x){
        sum += treeNum[x];
        x -= lowbit(x);
    }
    return sum;
}

void add(int x , int val){
    while(x < MAXN){
         treeNum[x] += val;
         x += lowbit(x);
    }
}

void solve(){
    // 把区间[x , y]每一点加上val
    add(x , val);
    add(y+1 , -val);
    // 计算点k的值
    int num = getSum(k);
}


         2 二维树状数组

     更改矩形[x1 , y1] - [x2 , y2],[x1 , y1] - [x2 , y2]里面的每个数全部加上val , 查询点(x , y)的值

         

          矩形[x1 , y1] - [x2 , y2]里面的每一个元素加上val相当于点(x1 , y1)加上val , 点(x1 , y2+1)减去val,点(x2+1 , y1)减去val , 点(x2+1 , y2+1)加上val。那么求某个点(x , y)的值即求[1 , 1] - [x , y]的和

           

  1. int lowbit(int x){  
  2.     return x&(-x);  
  3. }  
  4.   
  5. int getSum(int x , int y){  
  6.     int sum = 0;  
  7.     for(int i = x ; i > 0 ; i -= lowbit(i))  
  8.        for(int j = y ; j > 0 ; j -= lowbit(j))  
  9.            sum += treeNum[i][j];  
  10.     return sum;  
  11. }  
  12.   
  13. void add(int x , int y , int val){  
  14.     for(int i = x ; i < MAXN ; i += lowbit(i))  
  15.        for(int j = y ; j < MAXN ; j += lowbit(j))  
  16.            treeNum[i][j] += val;  
  17. }  
  18.   
  19. void solve(){  
  20.      // 矩形[x1 , y1]-[x2 , y2]每个点加上val  
  21.      add(x1 , y1 , val);   
  22.      add(x2+1 , y1 , -val);   
  23.      add(x1 , y2+1 , -val);   
  24.      add(x2+1 , y2+1 , val);   
  25.      // 求点(x , y)的值  
  26.      int num = getSum(x , y);  
  27. }  
int lowbit(int x){
    return x&(-x);
}

int getSum(int x , int y){
    int sum = 0;
    for(int i = x ; i > 0 ; i -= lowbit(i))
       for(int j = y ; j > 0 ; j -= lowbit(j))
           sum += treeNum[i][j];
    return sum;
}

void add(int x , int y , int val){
    for(int i = x ; i < MAXN ; i += lowbit(i))
       for(int j = y ; j < MAXN ; j += lowbit(j))
           treeNum[i][j] += val;
}

void solve(){
     // 矩形[x1 , y1]-[x2 , y2]每个点加上val
     add(x1 , y1 , val); 
     add(x2+1 , y1 , -val); 
     add(x1 , y2+1 , -val); 
     add(x2+1 , y2+1 , val); 
     // 求点(x , y)的值
     int num = getSum(x , y);
}



5 常用的技巧

   假设初始化数组每个点的值为1,那么我们知道对于一维的树状数组来说,我们知道treeNum[i] = lowbit(i) . 对于二维树状数组来说treeNum[i][j] = lowbit(i)*lowbit(j)

    

  1. void init(){  
  2.     // 一维  
  3.     memset(treeNum , 0 , sizeof(treeNum));  
  4.     for(int i = 1 ; i < MAXN ; i++){  
  5.         num[i] =1;  
  6.         treeNum[i] = lowbit(i);  
  7.     }  
  8.       
  9.     // 二维  
  10.     memset(treeNum , 0 , sizeof(treeNum));  
  11.     for(int i = 1 ; i < MAXN ; i++){  
  12.        for(int j = 1 ; j < MAXN ; j++){  
  13.            num[i][j] = 1;  
  14.            treeNum[i][j] = lowbit(i)*lowbit(j);  
  15.        }  
  16.     }  
  17. }  
void init(){
    // 一维
    memset(treeNum , 0 , sizeof(treeNum));
    for(int i = 1 ; i < MAXN ; i++){
        num[i] =1;
        treeNum[i] = lowbit(i);
    }

    // 二维
    memset(treeNum , 0 , sizeof(treeNum));
    for(int i = 1 ; i < MAXN ; i++){
       for(int j = 1 ; j < MAXN ; j++){
           num[i][j] = 1;
           treeNum[i][j] = lowbit(i)*lowbit(j);
       }
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
树状数组(Fenwick Tree)是一种用于快速维护数组前缀和的数据结构。它可以在 $O(\log n)$ 的时间内完成单点修改和前缀查询操作,比线段树更加简洁高效。 下面是 Java 实现的树状数组详解: 首先,在 Java 中我们需要使用数组来表示树状数组,如下: ``` int[] tree; ``` 接着,我们需要实现两个基本操作:单点修改和前缀查询。 单点修改的实现如下: ``` void update(int index, int value) { while (index < tree.length) { tree[index] += value; index += index & -index; } } ``` 该函数的参数 `index` 表示要修改的位置,`value` 表示修改的值。在函数内部,我们使用了一个 `while` 循环不断向上更新树状数组中相应的节点,直到到达根节点为止。具体来说,我们首先将 `tree[index]` 加上 `value`,然后将 `index` 加上其最后一位为 1 的二进制数,这样就可以更新其父节点了。例如,当 `index` 为 6 时,其二进制表示为 110,最后一位为 2^1,加上后变为 111,即 7,这样就可以更新节点 7 了。 前缀查询的实现如下: ``` int query(int index) { int sum = 0; while (index > 0) { sum += tree[index]; index -= index & -index; } return sum; } ``` 该函数的参数 `index` 表示要查询的前缀的结束位置,即查询 $[1, index]$ 的和。在函数内部,我们同样使用了一个 `while` 循环不断向前查询树状数组中相应的节点,直到到达 0 为止。具体来说,我们首先将 `sum` 加上 `tree[index]`,然后将 `index` 减去其最后一位为 1 的二进制数,这样就可以查询其前一个节点了。例如,当 `index` 为 6 时,其二进制表示为 110,最后一位为 2^1,减去后变为 100,即 4,这样就可以查询节点 4 的值了。 最后,我们还需要初始化树状数组,将其全部置为 0。初始化的实现如下: ``` void init(int[] nums) { tree = new int[nums.length + 1]; for (int i = 1; i <= nums.length; i++) { update(i, nums[i - 1]); } } ``` 该函数的参数 `nums` 表示初始数组的值。在函数内部,我们首先创建一个长度为 `nums.length + 1` 的数组 `tree`,然后逐个将 `nums` 中的元素插入到树状数组中。具体来说,我们调用 `update(i, nums[i - 1])` 来将 `nums[i - 1]` 插入到树状数组的第 `i` 个位置。 到此为止,我们就完成了树状数组的实现。可以看到,树状数组的代码比线段树要简洁很多,而且效率也更高。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值