【数据结构】树状数组

树状数组

假设一个数可以 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 cx cx的含义是以 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} ai ∑i=x−lowbit(x)+1xai

单点更新:

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 cx cx的父节点是 c x + l o w b i t ( x ) cx + lowbit(x) cx+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 bl = bl + c bl=bl+c, b r + 1 = b r + 1 − c br + 1 = br + 1 - c br+1=br+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 bl = bl + c bl=bl+c, b r + 1 = b r + 1 − c br + 1 = br + 1 - c br+1=br+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 bi i×bi数组的前缀和。可以用树状数组维护。

考虑扩展三

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

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

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

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

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

相关推荐
AI小老六27 分钟前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术1 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize2 小时前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考15 小时前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
CSharp精选营18 小时前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
美团技术团队19 小时前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
To_OC1 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode
To_OC1 天前
LC 208 实现 Trie 前缀树:曾被名字劝退,写完发现是送分题
javascript·算法·leetcode
BadBadBad__AK2 天前
线段树维护区间 k 次方和
c++·数学·算法·stl