美文网首页
树状数组[要点]

树状数组[要点]

作者: hynial | 来源:发表于2020-07-18 14:18 被阅读0次

Advise Category: Algorithm >> 树状数组

Scenario

  1. 单点更新
  2. 区间求和(前缀和)

Objects

  1. 待处理数组,a[1...n]
  2. 待维护树状数组,c[1...n]
  3. 结果数组(前缀和)(待处理数组的前缀和),r[1...n]

Ideas

Q:

r[n] = a[1] + ... + a[n]

A1:

每次计算r[i]都遍历a的前i项,一个结果r[i]一次遍历,n个结果r[n]需要n次遍历(不适合大数据数组的情况)。

A2:(树状)
?如何加速遍历

不与a[i]为维度进行运算,而是通过维护一个数组(数组)c[1...n]其中每一项是通过一定规律的m项和,再通过规律找到前缀和r[i]的待累积c[i]

并求和。

Method for A2

a[i] -> c[i]:
c[1] = c[0001] = a[1];
c[2] = c[0010] = a[1]+a[2];
c[3] = c[0011] = a[3];
c[4] = c[0100] = a[1]+a[2]+a[3]+a[4];
c[5] = c[0101] = a[5];
c[6] = c[0110] = a[5]+a[6];
c[7] = c[0111] = a[7];
c[8] = c[1000] = a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];
......
树状.png
Rule:
c[i]=a[i-2^k+1]+a[i-2^k+2]+......a[i];

Note:

  • ki的二进制中从最低位到高位连续零的长度, 例如i=8(1000)时,k=3
  • 可以理解为这是一种分类方法,通过维护分类(要么有零,要么没零,有零说明进位了需要把其下的数都考虑在内)数组c[i]让前缀求和变得更快。

单点更新

?如果修改a中的一个元素,c中的元素如何变化

Assumption:a[3] = a[3] + 1,从3往后找,直到数组结束。

lowbit(3)=0001=2^0 3+lowbit(3)=04(00100)    c[04] += 1
lowbit(4)=0100=2^2 4+lowbit(4)=08(01000)    c[08] += 1
lowbit(8)=1000=2^3 8+lowbit(8)=16(10000)    c[16] += 1
......

Note:

  • 可以看出a[3]变化之后,会涉及到c[4]/c[8]/[16]...的变化,所以需要更新跟随变化的c中的元素。
?lowbit

lowbit(x)是取出x的最低位1(从右往左数第一个1),满足:

int lowbit(x){ return x & (-x); }

Note:

  • 一个数的负数就等于对这个数取反+1

  • 补码和原码必然相反,所以原码有0的部位补码全是1,补码再+1之后由于进位那么最末尾的1和原码最右边的1一定是同一个位置

  • 刚好等于2^kkx的二进制中从最低位到高位连续零的长度

Code:

void update(int x,int y,int n){
    for(int i=x; i <= n; i += lowbit(i))    //x为更新的位置,y为更新后的数,n为数组最大值
        c[i] += y;
}

区间求和

e.g 求r[5]

Disappear:

c[4]=a[1]+a[2]+a[3]+a[4];
c[5]=a[5];

sum(i = 5) = c[4] + c[5];
sum(i = 101) = c[(100)] + c[(101)];

Note:

  • 首次从101,减去最低位的1就是100,刚好是单点更新的你操作。

Code:

int sum(int x){
    int ans = 0;
    for(int i = x; i >= 0; i -= lowbit(i))
        ans += c[i];
        
    return ans;
}

Example

leet-code:

计算右侧小于当前元素的个数

Ans:

class Solution{
   public List<Integer> countSmaller3(int[] nums){
        if(nums == null) return null;
        if(nums.length == 0) return new ArrayList<>();

        List<Integer> result = new ArrayList<>();
        Set<Integer> set = new HashSet<Integer>();
        for (int i = 0; i < nums.length; i++) {
            set.add(nums[i]);
        }

        int[] c = Arrays.stream(set.toArray()).sorted().mapToInt(e -> Integer.parseInt(e.toString())).toArray();
        int[] d = new int[c.length + 2];
        for (int i = nums.length - 1; i > -1; i--) {
            int idx = Arrays.binarySearch(c, nums[i]) + 1;
            int s = sum(d, idx - 1);
            update(d, idx, 1);
            result.add(s);
        }

        Collections.reverse(result);
        return result;
    }

    private int lowbit(int x){
        return x & (-x);
    }

    private void update(int[] c, int i, int delta){
        while( i <= c.length - 1 ){
            c[i] += delta;
            i += lowbit(i);
        }
    }

    private int sum(int[] c, int i){
        int ans = 0;
        while( i > 0 ){
            ans += c[i];
            i -= lowbit(i);
        }

        return ans;
    }
}

相关文章

  • 树状数组[要点]

    Advise Category: Algorithm >> 树状数组 Scenario 单点更新 区间求和(前缀和...

  • 数据结构(二)

    树状数组 POJ 1990: MooFest关于x坐标建立树状数组。先按照v值升序排序,逐个添加到树状数组里。每次...

  • 三种一维树状数组

    单点修改+区间查询 最基本的树状数组 树状数组入门 模板(洛谷P3374 【模板】树状数组1) 区间修改+单点查询...

  • 树状结构转一维数组

    树状结构转数组方法 声明树状对象

  • 树状数组

    复习一下树状数组 树状数组 一种用于处理单点修改和区间查询的数据结构。树状数组C的定义: C[x] = Sum ...

  • 树状数组模板复习

    树状数组模板复习

  • 树状数组求逆序对原理

    归并排序和树状数组都可以用nlogn的算法做到求出逆序对.但这里着重讲树状数组的原理与求法.树状数组最常用的方面就...

  • 「树状数组」

    题目传送门 poj1990题意: 农夫的N (N∈[1, 20,000])头牛参加了"MooFest"之后有了不...

  • 树状数组

    参见: https://www.cnblogs.com/hsd-/p/6139376.htmlhttps://bl...

  • 树状数组

    created by Dejavu*[完结] 简介树状数组(Binary Indexed Tree(BIT), F...

网友评论

      本文标题:树状数组[要点]

      本文链接:https://www.haomeiwen.com/subject/uiaxkktx.html