学习笔记-树状数组
蒟蒻的数据结构学习笔记
01 Introduction
它是什么? 树状数组是一种支持单点修改和区间查询的数据结构。
它长什么样?它用普通数组实现,数组长度与原数组相同。
它的效率如何? 它的修改时间复杂度是 $O(\log n)$,区间查询时间复杂度也为 $O(\log n)$。
后文记树状数组为$c$,原数组为$a$。
02 Structure
先来认识一个新朋友: lowbit()
函数。
lowbit(x)
表示 x
在二进制表示下,最右边的$1$所占的二进制权值。
例如:$44=(00101100)_2$ ,最右边的0在从右往左数第三位,因此lowbit(x)
$=(00000100)_2$即$4$。
如何实现?
int lowbit(int x) { return x & (-x); }
具体什么原理我也不知道。但把这板子背下来应该不难吧
而树状数组的存储方式,正是基于这个函数。
树状数组的每个 $c_i$ ,正是表示从 $a_{i-lowbit(i)+1}$ 到 $a_i$ 的区间和。
这段区间的长度正好为 lowbit(x)
。
换言之,
$c_i=\sum_{j=i-lowbit(i)+1}^ia_j$.
03 Template
它的初始化就很简单了:
void BinaryIndexedTree::init() // 初始化,复杂度O(n^2) { for (int i = 1; i <= len; i++) { for (int j = i - lowbit(i) + 1; j <= i; j++) // 每个c[i]都代表长度为lowbit[i],结束于i的子段之和 { c[i] += a[j]; } } }
那么,如何实现单点修改呢?
实际上也很简单:
void BinaryIndexedTree::add(int index, int x) // 支持单点修改,复杂度O(log n) { for (int i = index; i <= len; i += lowbit(i)) { c[i] += x; } }
index
代表要更改的原数组下标。
每次加上一个lowbit[i]
可以理解为向前进一位,这一位一定包含 a[index]
(目前我没法理解如何证明这个,请dalao指导)
区间查询,如果只查一次就只能查到从1开始的。
int BinaryIndexedTree::getSum(int index) // 求1~index区间和,复杂度O(log n) { if (!index) { return 0; } int ret = 0, lbit = lowbit(index); ret = c[index] + getSum(index - lbit); // 问题转化为求index-lbit+1~index的区间和即c[i],再求前面1~index-lbit区间和 return ret; }
可以看到这是一个递归,我们实际上每次都把问题转化为求$index-lbit+1 --index$的区间和即$c_i$,再求前面$1--index-lbit$区间和。
如果要查询一个不从1开始的区间,就查两次:
int BinaryIndexedTree::sum(int start, int end) // 求start~end区间和,复杂度O(log n) { int ret = getSum(end) - getSum(start - 1); return ret; }
Congratulations!
恭喜你!现在,你已经完成了整个树状数组的板子。
如果你理解了它的基本存储原理和查询方式,那么我们来看点拓展。
04 Expansion
有的时候,我们需要利用树状数组同时实现区间修改和单点查询。
那怎么办呢?
曾经我们求区间和使用的算法是什么?差分!
实际上,我们可以使用树状数组套一个差分数组,即可实现区间修改,查询两次复杂度仍然为 $O(\log n)$。
初始化和普通差分数组相同:
for (int i = 1; i <= n; i++) { int tmp; scanf("%d", &tmp); add(i, tmp); add(i + 1, -tmp); }
区间修改也比较简单:
int l, r, tmp; scanf("%d%d%d", &l, &r, &tmp); add(l, tmp); add(r+1, tmp);
这样做的坏处在于,由于使用了差分数组,树状数组的区间查询功能就变成了单点查询功能。
也就是说,要不区间查询,要不区间修改,二选一。
如何实现单点查询?根据差分数组的性质,求差分数组的前缀和即可,也就是树状数组的区间查询功能。
int k; scanf("%d". &k); printf("%d\n", getSum(k));
上面就是树状数组的全部核心内容。
理解差分思想很重要!
放几道例题:
本文作者:aaaaaaqqqqqq
本文链接:https://www.cnblogs.com/aaaaaaqqqqqq/p/17976970
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!