树状数组

🌲树状数组

🌿 为什么要有树状数组

考虑以下情形

现有一个数组A[n],讨论以下两种操作的复杂度

  1. 计算前缀和A.sum(1, m)
  2. 修改任意一个元素A[i]

可以有的解法:

  • 第一种:对于原始数组,求和操作的复杂度是 O ( n ) O(n) O(n),修改操作的复杂度是 O ( 1 ) O(1) O(1)
  • 第二种:可以构建一个辅助数组sum[n],表示前缀和。这样,求和操作的复杂度就变成了 O ( 1 ) O(1) O(1),而修改操作为 O ( n ) O(n) O(n)
  • 第三种:有没有一种数据结构可以折中一下呢?树状数组:它的求和复杂度为 O ( l o g n ) O(logn) O(logn),修改复杂度也是 O ( l o g n ) O(logn) O(logn)
🌿 树状数组的结构

在原数组A[n]的基础上构建一个辅助数组,记作C[n]注意:下标从1开始。

🍃 lowBit
  • 它表示:一个数用二进制表示时,最低位的1所表示的值。
  • 另一重含义:它表示一个数的最大因数,这个因数必须是2的k次幂
  • 计算lowBit的方法:lowBit = i&(-i)。原理:补码,取反加一。
  • 比如:
    • 24:二进制表示为11000lowBit = 8
    • 12:二进制表示为1100lowBit = 4
🍃 C[i]的含义
  • C[i]表示:数组A[n]中,包括A[i]在内的前方lowBit个元素的总和。

C [ i ] = ∑ t = 0 l o w B i t A [ i − t ] C[i] = \sum_{t=0}^{lowBit}A[i-t] C[i]=t=0lowBitA[it]

  • C[i]还可以使用数组C[n]中的其他元素来表示:
    • 对于i来说: 2 k = l o w B i t 2^k = lowBit 2k=lowBiti的二进制表示中,末尾有k个0;C[i]表示A[n] 2 k 2^k 2k个元素的和;
    • t < k t<k t<k,对于 i − 2 t i-2^t i2t,它的末尾有t个0;那么 C [ i − 2 t ] C[i-2^t] C[i2t]表示A[n] 2 t 2^t 2t个元素的和;
    • 2 k 2^k 2k可以写成 2 k = 1 + ∑ t = 0 k − 1 2 t 2^k=1+\sum_{t=0}^{k-1}2^t 2k=1+t=0k12t,所以C[i]可以用其他的C[n]表示为:

C [ i ] = A [ i ] + ∑ t = 0 k − 1 C [ i − 2 t ] , 其 中 2 k = l o w B i t C[i] = A[i] + \sum_{t=0}^{k - 1}C[i-2^t],其中2^k=lowBit C[i]=A[i]+t=0k1C[i2t]2k=lowBit

🍃 父子结点
  • C[i]的父节点是C[i+lowBit]
  • C[i]的子节点共有k + 1个,其中 2 k = l o w B i t 2^k = lowBit 2k=lowBit
🌿 树状数组的操作
🍃 构造

通过C[i]的子节点来构造C[i],复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

void Init(vector<int> A, vector<int> C) {
	for (int i = 1; i < A.size(); ++i) {
    	int lowBit = i & (-i);
        C[i] = A[i];
        for (int pf = 1; pf < lowBit; pf <<= 1) {
        	C[i] += C[i - pf];
        }
    }
}
🍃 前缀和

复杂度为 O ( l o g n ) O(logn) O(logn)

// 计算前缀和,闭区间A[1, m]
int PrefixSum(vector<int> C, int m) {
	int sum = 0;
    while (m > 0) {
    	sum += C[m];
        m -= (m & (-m));
    }
    return sum;
}
🍃 区间和
// 计算区间和,闭区间C[a, b]
int IntervalSum(vector<int> C, int a, int b) { 
	return PrefixSum(C, b) - PrefixSum(C, a - 1);
}
🍃 查询

直接查询A[n]即可,所以复杂度为 O ( 1 ) O(1) O(1)

🍃 修改

找到所有的祖先结点即可,复杂度为 O ( l o g n ) O(logn) O(logn)

void Update(vector<int>& A, vector<int>& C, int index, int value) {
    int delta = value - A[index];
    A[index] = value;
    // 受影响的结点都增加delta即可
    while (index < c.size()) {
    	c[index] += delta;
        index += (i & (-i));	// 定位到父节点
    }
}

🌳 拓宽视野

🌾 差分树状数组

优点是:修改区间,查询点

A[i]的基础上构建差分数组D[i] = A[i] - A[i - 1]。然后在差分数组的基础上构建树状数组C[i],这样可以实现区间更新和单点查询了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值