学习笔记-树状数组

蒟蒻的数据结构学习笔记

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));

上面就是树状数组的全部核心内容。

理解差分思想很重要!

放几道例题:

树状数组

树状数组+差分

NKOJ

本文作者:aaaaaaqqqqqq

本文链接:https://www.cnblogs.com/aaaaaaqqqqqq/p/17976970

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

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