欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 培训 > 【数据结构】树状数组

【数据结构】树状数组

2025/5/19 17:22:30 来源:https://blog.csdn.net/Doctor_Anonymous/article/details/148045828  浏览:    关键词:【数据结构】树状数组

树状数组

假设一个数可以 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+...+2im1+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=xlowbit(x)+1xa[i]

image-20250518143140939

单点更新:

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

区间查询:

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=xlowbit(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=1rj=1ibj

对应代码为:

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;in;i++) cnt[i]=1 表示 1 1 1 n n n中所有数均可用。

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

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

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

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词