【数据结构】树状数组

树状数组

假设一个数可以 x x x可以被二进制分解成 x = 2 i 1 + 2 i 2 + . . . + 2 i m x = 2^{i_1} + 2^{i_2} + ... + 2^{i_m} x=2i1+2i2+...+2im,不妨设 i 1 > i 2 > . . . > i m i_1 > i_2 > ... > i_m i1>i2>...>im,进一步地,区间 [ 1 , x ] [1, x] [1,x]可以分成 O ( l o g x ) O(log\ x) O(log x)个小区间:

  • 长度为 2 i 1 2^{i_1} 2i1的小区间 [ 1 , 2 i 1 ] [1, 2^{i_1}] [1,2i1]
  • 长度为 2 i 2 2^{i_2} 2i2的小区间 [ 2 i 1 + 1 , 2 i 1 + 2 i 2 ] [2^{i_1} + 1, 2^{i_1} + 2^{i_2}] [2i1+1,2i1+2i2]
  • . . . ... ...
  • 长度为 2 i m 2^{i_m} 2im的小区间 [ 2 i 1 + 2 i 2 + . . . + 2 i m − 1 + 1 , 2 i 1 + 2 i 2 + . . . + 2 i m ] [2^{i_1} + 2^{i_2} + ... + 2^{i_{m - 1}} + 1, 2^{i_1} + 2^{i_2} + ... + 2^{i_m}] [2i1+2i2+...+2im−1+1,2i1+2i2+...+2im]

这些小区间的共同特点是:若区间结尾为 R R R,则区间长度为 l o w b i t ( R ) lowbit(R) lowbit(R)。

树状数组,就是基于上述思想的数据结构,其基本用途是维护序列的前缀和。

对于给定的序列 a a a,建立数组 c c c,其中 c [ x ] c[x] c[x]的含义是以 x x x为结尾的,长度为 l o w b i t ( x ) lowbit(x) lowbit(x)的区间中所有数的和,即 ∑ i = x − l o w b i t ( x ) + 1 x a [ i ] \sum_{i = x - lowbit(x) + 1}^{x} a[i] ∑i=x−lowbit(x)+1xa[i]。

单点更新:

c 复制代码
void add(int x, int t) {
	for (int i = x; i <= n; i += lowbit(i)) c[i] += t;
}

区间查询:

c 复制代码
int ask(int x) {
	int res = 0;
	for (int i = x; i; i -= lowbit(i)) res += c[i];
}

对于查询:

我们想要得知以区间 [ 1 , x ] [1, x] [1,x]的和,不妨从树状数组区间长度的划分和数组 c c c的定义出发。我们知道,对于长度为 x x x的区间,我们可以每次将其划分为长度为 l o w b i t ( x ) lowbit(x) lowbit(x)的区间,并 x = x − l o w b i t ( x ) x = x - lowbit(x) x=x−lowbit(x)。所以,我们很容易得到查询的代码。

复杂度是 O ( l o g n ) O(log\ n) O(log n)。

对于更新:

除树根外,每个内部节点 c [ x ] c[x] c[x]的父节点是 c [ x + l o w b i t ( x ) ] c[x + lowbit(x)] c[x+lowbit(x)]。

复杂度是 O ( l o g n ) O(log \ n) O(log n)。


树状数组的扩展应用

以上介绍了树状数组的基本应用,即:

  1. 修改数组中的一个数。
  2. 查询区间和。

树状数组还有一些扩展应用,如:

扩展一:

  1. 将区间 [ l , r ] [l, r] [l,r]加上 c c c。
  2. 查询某个位置上的数。

扩展二:

  1. 将区间 [ l , r ] [l, r] [l,r]加上 c c c。
  2. 查询区间和。

扩展三: 结合其他算法(如:二分)实现更多功能

考虑扩展一

对于给定的序列 a a a,考虑其差分数组 b b b。

若想将区间 [ l , r ] [l, r] [l,r]加上一个数,可以对应在差分数组上执行 b [ l ] = b [ l ] + c b[l] = b[l] + c b[l]=b[l]+c, b [ r + 1 ] = b [ r + 1 ] − c b[r + 1] = b[r + 1] - c b[r+1]=b[r+1]−c。

若想查询某个位置 x x x的数,即求位置 x x x的前缀和。

以上两个操作是单点修改,区间查询。可以用树状数组解决。

考虑扩展二

对于区间给定的序列 a a a,考虑其差分数组 b b b。

若想将区间 [ l , r ] [l, r] [l,r]加上一个数,可以对应在差分数组上执行 b [ l ] = b [ l ] + c b[l] = b[l] + c b[l]=b[l]+c, b [ r + 1 ] = b [ r + 1 ] − c b[r + 1] = b[r + 1] - c b[r+1]=b[r+1]−c。

若想查询区间 [ 1 , r ] [1, r] [1,r]的和,即 ∑ i = 1 r ∑ j = 1 i b j \sum_{i = 1}^r \sum_{j = 1}^{i}b_j ∑i=1r∑j=1ibj

对应代码为:

c 复制代码
for (int i = 1; i <= r; i ++) 
	for (int j = 1; j <= i; j ++) 
		ans += b[j];

考虑以下表格

a 1 a_1 a1 b 1 b_1 b1
a 2 a_2 a2 b 1 b_1 b1 b 2 b_2 b2
a 3 a_3 a3 b 1 b_1 b1 b 2 b_2 b2 b 3 b_3 b3
. . . ... ...
a i a_i ai b 1 b_1 b1 b 2 b_2 b2 . . . ... ... b m b_m bm

a 1 = b 1 a_1 = b_1 a1=b1

a 2 = b 1 + b 2 a_2 = b_1 + b_2 a2=b1+b2

a 3 = b 1 + b 2 + b 3 a_3 = b_1 + b_2 + b_3 a3=b1+b2+b3

. . . ... ...

a i = b 1 + b 2 + . . . + b m a_i = b_1 + b_2 + ... + b_m ai=b1+b2+...+bm

则 a 1 + a 2 + . . . + a i = ( b 1 + b 2 + . . . + b m ) × ( i + 1 ) − ( b 1 + 2 × b 2 + 3 × b 3 + . . . + m × b m ) a_1 + a_2 + ... + a_i = (b_1 + b_2 + ... + b_m) \times (i + 1) - (b_1 + 2 \times b_2 + 3\times b_3 + ... + m\times b_m) a1+a2+...+ai=(b1+b2+...+bm)×(i+1)−(b1+2×b2+3×b3+...+m×bm)

可以看出,其转化成了 b b b数组的前缀和,以及 i × b [ i ] i\times b[i] i×b[i]数组的前缀和。可以用树状数组维护。

考虑扩展三

考虑如下问题,给定 f o r ( i = 1 ; i ≤ n ; i + + ) c n t [ i ] = 1 for\ (i = 1; i \leq n; i ++) \ cnt[i] = 1 for (i=1;i≤n;i++) cnt[i]=1 表示 1 1 1到 n n n中所有数均可用。

现需要:每次找到所有可用数中第 k k k小的数,将该数存下,并将该数标记为不可用。

考虑维护数组 c n t cnt cnt的前缀和数组,标记为不可用只需将 1 ← 0 1 \leftarrow 0 1←0。

前缀和每次可以算出有多少可用的数,然后可以二分。

所以该问题需要的操作是:单点修改和区间查询。配合二分可以实现更多功能。

相关推荐
Shaun_青璇13 分钟前
Cpp 知识3
开发语言·c++·算法
小鸡脚来咯1 小时前
ThreadLocal实现原理
java·开发语言·算法
编程绿豆侠1 小时前
力扣HOT100之技巧:75. 颜色分类
算法·leetcode·排序算法
程序员Xu1 小时前
大厂机试题解法笔记大纲+按知识点分类+算法编码训练
笔记·算法
DarkChunk2 小时前
[BUG]Cursor C++扩展不支持
算法
怀旧,2 小时前
【数据结构】7. 栈和队列
数据结构
冠离sir2 小时前
代码随想录训练营第三十天 | 452. 用最少数量的箭引爆气球 435. 无重叠区间 763.划分字母区间
算法
数据与人工智能律师2 小时前
数据淘金时代:公开爬取如何避开法律雷区?
网络·人工智能·算法·云计算·区块链
闻缺陷则喜何志丹2 小时前
【强连通分量 拓扑序】P9431 [NAPC-#1] Stage3 - Jump Refreshers|普及+
c++·算法·图论·拓扑序·洛谷·强连通分量
liulilittle4 小时前
通过高级处理器硬件指令集AES-NI实现AES-256-CFB算法。
linux·服务器·c++·算法·安全·加密·openssl