树状数组

【树状数组是什么】

树状数组(Binary Indexed Tree, BIT)

支持单个元素修改 和 前缀查询。

比较一下:

子段和 修改单个元素
数组 O(n) O(1)
前缀和 O(1) O(n)
树状数组 O(logn) O(logn)

【树状数组的实现】

比如 a 数组有 16 个元素。

现在我们定义树状数组 t

t[i] 表示 j=ilowbit(i)+1i

其中 lowbit(x)=k 表示 2kx 但是 2k+1x ,即二进制下最低的是 1 的位 所代表的数。

形象一点:

先把所有是 16 的倍数的下标取出,它们的值是以下标结尾的 16 个元素的和。

在把 8 的倍数取出,同样,但是 16 的倍数就不管了。

循环往复,直到取完 1 的倍数。


如上图,就是 16 个元素的例子。


经过这样的处理,查询前缀和的复杂度是 log 级别 。

s[x]=s[xlowbit(x)]+t[x],而 s[xlowbit(x)] 又是一个同类型但更简单的子问题,递归求和即可。

相当于把 x 二进制拆位,因为 xlowbit(x) 就直接消去了最后一个 1,下一次的 lowbit() 就至少乘 2,所以查询前缀和的复杂度是 logn

单个元素修改的复杂度也是 log 级别

假设我们要修改 a[num]

首先所有下标小于 numti 一定不用修改。

然后 tnum 一定要修改。

再之后,如果 tx 要修改,那么 tx+lowbit(x) 也要修改。

证明:

tx+lowbit(x) 的管辖范围至少是 tx 的 2 倍。

tx 的管辖范围就是 lowbit(x)

所以 tx+lowbit(x) 可以管辖 a[x],a[x+1],...,a[x+lowbit(x)],还可以管辖 tx 的所有。

tx 可以管到 a[num],所以 tx+lowbit(x) 也要修改。

接着还有:除了上述元素,其他的元素都不用修改。

证明:

假设 tx 要修改,按照方法下一个修改的应该是 tx+lowbit(x)

我们只需要证明 tx+1,tx+2,...,tx+lowbit(x)1 都不修改即可。

而因为 x+lowbit(x) 恰使管辖范围增加,所以 x+1,x+2,...,x+lowbit(x)1 都不能使管辖范围增加。

范围不增加,终点还远了,当然管不到 anum

综上,我们只需要按照每次增加 lowbit(x) 的方式修改即可。

最后小问题:lowbit(x) 怎么算?

lowbit(x)=x & (x),按照补码算一下就证出来了。

【优缺点】

优点:

代码短、常数小。

缺点:

单点修改,可加不可减(比如求一个子段的最值,不能大减小),无法求任意子段,只能求前缀和。

【拓展】

  1. 树状数组优化 dp

一部分 dp 在转移的时候都是 dp[i]=max{dp[j]|j[1,i)}+k 的形式。

以前我们要用一重循环枚举 j,但是现在我们可以用树状数组快速求 dp[1] ~ dp[i1] 的最值,这刚好是一个前缀。

例子:最长上升子序列

先离散化。

dp[i] 表示以 i 号元素结尾的最长上升子序列长度。

当进行第 i 次循环,求出了 dp[i] 后,tr[a[i]]dp[i] 取 max,再赋值到 tr[a[i]]

这样我们要求 dp[i] 时,我们只需要求 tr[1] ~ tr[a[i]1]max 再加一即可。

  1. Generic Cow Protests G

基本想法:dp[i] 表示前 i 个分成的方案数。

dp[i] = dp[j]j[1,i),且 s[i]s[j]0s[i] 表示 a[1]+a[2]+...+a[i]

注意:s[i]s[j],考虑树状数组。

s[i] 为位置,dp[i] 为值。

posted @   FLY_lai  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示