【模板】树状数组

2023-11-17

1.概述

树状数组(Binary Indexed Trees),其基本用途是维护序列的前缀和。对于给定的序列 a [   ] a[\ ] a[ ],我们建立一个数组 c [   ] c[\ ] c[ ],其中 c [ x ] c[x] c[x] 保存序列 a [   ] a[\ ] a[ ] 的区间 [ x − l o w b i t ( x ) + 1 , x ] [x - lowbit(x) + 1, x] [xlowbit(x)+1,x] 中所有数的和。

事实上,数组 c [   ] c[\ ] c[ ] 可以看作一个如下图所示的树形结构,图中最下边一行是 N N N 个叶节点 ( N = 16 ) (N= 16) (N=16),代表数值 a [ 1 ∼ N ] a[1 \sim N] a[1N]。该结构满足以下性质:

  • 每个内部节点 c [ x ] c[x] c[x] 保存以它为根的子树中所有叶节点的和。

  • 每个内部节点 c [ x ] c[x] c[x] 的子节点个数等于 l o w b i t ( x ) lowbit(x) lowbit(x) 的位数。

  • 除树根外,每个内部节点 c [ x ] c[x] c[x] 的父节点是 c [ x + l o w b i t ( x ) ] c[x + lowbit(x)] c[x+lowbit(x)]

  • 树的深度为 O ( log ⁡ N ) O(\log N) O(logN)

  • 如果 N N N 不是 2 2 2 的整次幂,那么树状数组就是一个具有同样性质的森林结构。

在这里插入图片描述

2.原理

(1) 对于一个普通的二叉树,叶子结点代表数组 a [   ] a[\ ] a[ ] a [ 1 ] ∼ a [ 8 ] a[1] \sim a[8] a[1]a[8]

在这里插入图片描述

(2) 将其进行简单的变形。

在这里插入图片描述

(3) 然后定义每一列的顶端结点数组 c [   ] c[\ ] c[ ],令 c [ i ] c[i] c[i] 代表子树的叶结点的权值之和。

  • c [ 1 ] = a [ 1 ] c[1] = a[1] c[1]=a[1]
  • c [ 2 ] = a [ 1 ] + a [ 2 ] c[2] = a[1] + a[2] c[2]=a[1]+a[2]
  • c [ 3 ] = a [ 3 ] c[3] = a[3] c[3]=a[3]
  • c [ 4 ] = a [ 1 ] + a [ 2 ] + a [ 3 ] + a [ 4 ] c[4] = a[1] + a[2] + a[3] + a[4] c[4]=a[1]+a[2]+a[3]+a[4]
  • c [ 5 ] = a [ 5 ] c[5] = a[5] c[5]=a[5]
  • c [ 6 ] = a [ 5 ] + a [ 6 ] c[6] = a[5] + a[6] c[6]=a[5]+a[6]
  • c [ 7 ] = a [ 7 ] c[7] = a[7] c[7]=a[7]
  • c [ 8 ] = a [ 1 ] + a [ 2 ] + a [ 3 ] + a [ 4 ] + a [ 5 ] + a [ 6 ] + a [ 7 ] + a [ 8 ] c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]

在这里插入图片描述

(4) 将数组 c [   ] c[\ ] c[ ] 的结点序号转化为二进制。

  • 1 = ( 001 ) , c [ 1 ] = a [ 1 ] 1 = (001), c[1] = a[1] 1=(001),c[1]=a[1]

  • 2 = ( 010 ) , c [ 2 ] = a [ 1 ] + a [ 2 ] 2 = (010), c[2] = a[1] + a[2] 2=(010),c[2]=a[1]+a[2]

  • 3 = ( 011 ) , c [ 3 ] = a [ 3 ] 3 = (011), c[3] = a[3] 3=(011),c[3]=a[3]

  • 4 = ( 100 ) , c [ 4 ] = a [ 1 ] + a [ 2 ] + a [ 3 ] + a [ 4 ] 4 = (100), c[4] = a[1] + a[2] + a[3] + a[4] 4=(100),c[4]=a[1]+a[2]+a[3]+a[4]

  • 5 = ( 101 ) , c [ 5 ] = a [ 5 ] 5 = (101), c[5] = a[5] 5=(101),c[5]=a[5]

  • 6 = ( 110 ) , c [ 6 ] = a [ 5 ] + a [ 6 ] 6 = (110), c[6] = a[5] + a[6] 6=(110),c[6]=a[5]+a[6]

  • 7 = ( 111 ) , c [ 7 ] = a [ 7 ] 7 = (111), c[7] = a[7] 7=(111),c[7]=a[7]

  • 8 = ( 1000 ) , c [ 8 ] = a [ 1 ] + a [ 2 ] + a [ 3 ] + a [ 4 ] + a [ 5 ] + a [ 6 ] + a [ 7 ] + a [ 8 ] 8 = (1000), c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] 8=(1000),c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]

可以发现 c [ i ] = a [ i − 2 k + 1 ] + a [ i − 2 k + 2 ] + . . . . . . + a [ i ] c[i]=a[i-2^k+1]+a[i-2^k+2]+......+a[i] c[i]=a[i2k+1]+a[i2k+2]+......+a[i],其中, k k k i i i 的二进制中从最低位到高位连续零的长度。

可见,树状数组根本上来说就是二进制的应用。

要注意的是,树状数组只能计算 a [ 1 ] a[1] a[1] 开始的和, a [ 0 ] a[0] a[0] 这个元素是不能用的。

3.实现

3.1 lowbit(x)

l o w b i t ( x ) lowbit(x) lowbit(x) x x x 的二进制表达式中最低位的 1 1 1 所对应的值,换言之, l o w b i t ( x ) = 2 k lowbit(x)=2^k lowbit(x)=2k k k k i i i 的二进制中从最低位到高位连续零的长度。

对于一个数 x x x,想求 l o w b i t ( x ) lowbit(x) lowbit(x),可借助 x x x 的负数 − x -x x,进行按位与运算。

  • x = 6 = ( 0110 ) x = 6 = (0110) x=6=(0110),此时 k = 1 k=1 k=1

  • − x = − 6 = ( 1010 ) -x = -6 = (1010) x=6=(1010)

  • x x x & ( − x ) = ( 0010 ) = 2 1 (-x) = (0010) = 2^1 (x)=(0010)=21

// 返回x的二进制最右边1所对应的值
int lowbit(int x)
{
    return x & -x;
}

l o w b i t ( x ) lowbit(x) lowbit(x) 在树状数组中的应用:

c [ i ] = a [ i − 2 k + 1 ] + a [ i − 2 k + 2 ] + . . . . . . + a [ i ] = a [ i − l o w b i t ( i ) + 1 ] + a [ i − l o w b i t ( i ) + 2 ] + . . . . . . + a [ i ] \begin{aligned} c[i] & = a[i-2^k+1]+a[i-2^k+2]+......+a[i] \\ & = a[i-lowbit(i)+1]+a[i-lowbit(i)+2]+......+a[i] \end{aligned} c[i]=a[i2k+1]+a[i2k+2]+......+a[i]=a[ilowbit(i)+1]+a[ilowbit(i)+2]+......+a[i]

3.2 查询前缀和

树状数组支持的基本操作有两个,第一个操作是查询前缀和,即序列 a [   ] a[\ ] a[ ] 中第 1 ∼ x 1 \sim x 1x 个数的和。

首先求出 x x x 的二进制表示中每个等于 1 1 1 的位,然后把 [ 1 , x ] [1, x] [1,x] 分成 O ( log ⁡ N ) O(\log N) O(logN) 个小区间,而每个小区间的区间和都已经保存在数组 c [   ] c[\ ] c[ ] 中了。

下面代码在 O ( log ⁡ N ) O(\log N) O(logN) 的时间内查询前缀和:

// 返回a[1]+...+a[x]的和
int ask(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
    {
        res += c[i];
    }
    return res;
}

举例:求 a [ 1 ] + a [ 2 ] + . . . + a [ 7 ] a[1] + a[2] + ... + a[7] a[1]+a[2]+...+a[7] 的和

  • 7 ( 111 ) 7(111) 7(111)res += c[7]

  • 7 − l o w b i t ( 7 ) = 7 − l o w b i t ( 111 ) = 7 − 1 = 6 ( 110 ) 7-lowbit(7) = 7-lowbit(111) = 7-1 = 6(110) 7lowbit(7)=7lowbit(111)=71=6(110)res += c[6]

  • 6 − l o w b i t ( 6 ) = 6 − l o w b i t ( 110 ) = 6 − 2 = 4 ( 100 ) 6-lowbit(6) = 6-lowbit(110) = 6-2 = 4(100) 6lowbit(6)=6lowbit(110)=62=4(100)res += c[4]

  • 4 − l o w b i t ( 4 ) = 4 − l o w b i t ( 100 ) = 4 − 4 = 0 ( 000 ) 4-lowbit(4) = 4-lowbit(100) = 4-4 = 0(000) 4lowbit(4)=4lowbit(100)=44=0(000)

举例:求 a [ 1 ] + a [ 2 ] + . . . + a [ 5 ] a[1] + a[2] + ... + a[5] a[1]+a[2]+...+a[5] 的和

  • 5 ( 101 ) 5(101) 5(101)res += c[5]

  • 5 − l o w b i t ( 5 ) = 5 − l o w b i t ( 101 ) = 5 − 1 = 4 ( 100 ) 5-lowbit(5) = 5-lowbit(101) = 5-1 = 4(100) 5lowbit(5)=5lowbit(101)=51=4(100)res += c[4]

  • 4 − l o w b i t ( 4 ) = 4 − l o w b i t ( 100 ) = 4 − 4 = 0 ( 000 ) 4-lowbit(4) = 4-lowbit(100) = 4-4 = 0(000) 4lowbit(4)=4lowbit(100)=44=0(000)

当然,若要查询序列 a [   ] a[\ ] a[ ] 的区间 [ l , r ] [l,r] [l,r] 中所有数的和,只需计算 ask(r) - ask(l - 1)

3.3 单点增加

树状数组支持的第二个基本操作是单点增加,意思是给序列中的一个数 a [ x ] a[x] a[x] 加上 y y y,同时正确维护序列的前缀和。

根据上面给出的树形结构和它的性质,只有节点 c [ x ] c[x] c[x] 及其所有祖先节点保存的“区间和”包含 a [ x ] a[x] a[x],而任意一个节点的祖先至多只有 log ⁡ N \log N logN 个,我们逐一对它们的 c c c 值进行更新即可。

下面代码在 O ( log ⁡ N ) O(\log N) O(logN) 的时间内执行单点增加操作:

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

在这里插入图片描述

如图所示,当更新 a [ 1 ] a[1] a[1] 时,需要向上更新 c [ 1 ] c[1] c[1] c [ 2 ] c[2] c[2] c [ 4 ] c[4] c[4] c [ 8 ] c[8] c[8],则有:

  • 1 ( 001 ) 1(001) 1(001)c[1] += a[1]

  • 1 + l o w b i t ( 1 ) = 1 + l o w b i t ( 001 ) = 1 + 1 = 2 ( 010 ) 1+lowbit(1) = 1+lowbit(001) = 1+1 = 2(010) 1+lowbit(1)=1+lowbit(001)=1+1=2(010)c[2] += a[1]

  • 2 + l o w b i t ( 2 ) = 2 + l o w b i t ( 010 ) = 2 + 2 = 4 ( 100 ) 2+lowbit(2) = 2+lowbit(010) = 2+2 = 4(100) 2+lowbit(2)=2+lowbit(010)=2+2=4(100)c[4] += a[1]

  • 4 + l o w b i t ( 4 ) = 4 + l o w b i t ( 100 ) = 4 + 4 = 8 ( 1000 ) 4+lowbit(4) = 4+lowbit(100) = 4+4 = 8(1000) 4+lowbit(4)=4+lowbit(100)=4+4=8(1000)c[8] += a[1]

可以发现:更新过程是查询过程的逆过程。

4.初始化

在执行所有操作之前,我们需要对树状数组进行初始化,即针对原始序列 a [   ] a[\ ] a[ ] 构造一个树状数组 c [   ] c[\ ] c[ ]

为了简便,比较一般的初始化方法是:直接建立一个全为 0 0 0 的数组 c [   ] c[\ ] c[ ],然后对每个位置 x x x 执行 add(x, a[x]),就完成了对原始序列 a [   ] a[\ ] a[ ] 构造树状数组的过程,时间复杂度为 O ( N log ⁡ N ) O(N \log N) O(NlogN)通常采用这种初始化方法已经足够

更高效的初始化方法是:从小到大依次考虑每个节点 x x x,借助 l o w b i t lowbit lowbit 运算扫描它的子节点并求和。若采用这种方法,上面树形结构中的每条边只会被遍历一次,时间复杂度为 O ( N ) O(N) O(N)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【模板】树状数组 的相关文章

  • C# 和 Javascript SHA256 哈希的代码示例

    我有一个在服务器端运行的 C 算法 它对 Base64 编码的字符串进行哈希处理 byte salt Convert FromBase64String serverSalt Step 1 SHA256Managed sha256 new S
  • Qt-Qlist 检查包含自定义类

    有没有办法覆盖加载自定义类的 Qt QList 的比较机制 即在 java 中你只需要重写一个比较方法 我有一个带有我的自定义类模型的 QList QList
  • 将数组向左或向右旋转一定数量的位置,复杂度为 o(n)

    我想编写一个程序 根据用户的输入 正 gt 负 include
  • 将布尔参数传递给 SQL Server 存储过程

    我早些时候问过这个问题 我以为我找到了问题所在 但我没有 我在将布尔参数传递给存储过程时遇到问题 这是我的 C 代码 public bool upload false protected void showDate object sende
  • WPF 中的调度程序和异步等待

    我正在尝试学习 WPF C 中的异步编程 但我陷入了异步编程和使用调度程序的困境 它们是不同的还是在相同的场景中使用 我愿意简短地回答这个问题 以免含糊不清 因为我知道我混淆了 WPF 中的概念和函数 但还不足以在功能上正确使用它 我在这里
  • 指针问题(仅在发布版本中)

    不确定如何描述这一点 但我在这里 由于某种原因 当尝试创建我的游戏的发布版本进行测试时 它的敌人创建方面不起作用 Enemies e level1 3 e level1 0 Enemies sdlLib 500 2 3 128 250 32
  • 将目录压缩为单个文件的方法有哪些

    不知道怎么问 所以我会解释一下情况 我需要存储一些压缩文件 最初的想法是创建一个文件夹并存储所需数量的压缩文件 并创建一个文件来保存有关每个压缩文件的数据 但是 我不被允许创建许多文件 只能有一个 我决定创建一个压缩文件 其中包含有关进一步
  • C 预处理器库

    我的任务是开发源分析工具C程序 并且我需要在分析本身之前预处理代码 我想知道什么是最好的图书馆 我需要一些重量轻 便于携带的东西 与其推出自己的 为什么不使用cpp这是的一部分gcc suite http gcc gnu org onlin
  • 如何将图像路径保存到Live Tile的WP8本地文件夹

    我正在更新我的 Windows Phone 应用程序以使用新的 WP8 文件存储 API 本地文件夹 而不是 WP7 API 隔离存储文件 旧的工作方法 这是我如何成功地将图像保存到 共享 ShellContent文件夹使用隔离存储文件方法
  • 将自定义元数据添加到 jpeg 文件

    我正在开发一个图像处理项目 C 我需要在处理完成后将自定义元数据写入 jpeg 文件 我怎样才能做到这一点 有没有可用的图书馆可以做到这一点 如果您正在谈论 EXIF 元数据 您可能需要查看exiv2 http www exiv2 org
  • Github Action 在运行可执行文件时卡住

    我正在尝试设置运行google tests on a C repository using Github Actions正在运行的Windows Latest 构建过程完成 但是当运行测试时 它被卡住并且不执行从生成的可执行文件Visual
  • 当操作繁忙时,表单不执行任何操作(冻结)

    我有一个使用 C 的 WinForms 应用程序 我尝试从文件中读取一些数据并将其插入数据表中 当此操作很忙时 我的表单冻结并且无法移动它 有谁知道我该如何解决这个问题 这可能是因为您在 UI 线程上执行了操作 将文件和数据库操作移至另一个
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 将 unsigned char * (uint8_t *) 转换为 const char *

    我有一个带有 uint8 t 参数的函数 uint8 t ihex decode uint8 t in size t len uint8 t out uint8 t i hn ln for i 0 i lt len i 2 hn in i
  • C++ 复制初始化和直接初始化,奇怪的情况

    在继续阅读本文之前 请阅读在 C 中 复制初始化和直接初始化之间有区别吗 https stackoverflow com questions 1051379 is there a difference in c between copy i
  • 如何使我的表单标题栏遵循 Windows 深色主题?

    我已经下载了Windows 10更新包括黑暗主题 文件资源管理器等都是深色主题 但是当我创建自己的 C 表单应用程序时 标题栏是亮白色的 如何使我自己的桌面应用程序遵循我在 Windows 中设置的深色主题 你需要调用DwmSetWindo
  • 插入记录后如何从SQL Server获取Identity值

    我在数据库中添加一条记录identity价值 我想在插入后获取身份值 我不想通过存储过程来做到这一点 这是我的代码 SQLString INSERT INTO myTable SQLString Cal1 Cal2 Cal3 Cal4 SQ
  • 将文本叠加在图像背景上并转换为 PDF

    使用 NET 我想以编程方式创建一个 PDF 它仅包含一个背景图像 其上有两个具有不同字体和位置的标签 我已阅读过有关现有 PDF 库的信息 但不知道 如果适用 哪一个对于如此简单的任务来说最简单 有人愿意指导我吗 P D 我不想使用生成的
  • 如何使用 std::string 将所有出现的一个字符替换为两个字符?

    有没有一种简单的方法来替换所有出现的 in a std string with 转义 a 中的所有斜杠std string 完成此操作的最简单方法可能是boost字符串算法库 http www boost org doc libs 1 46
  • 防止索引超出范围错误

    我想编写对某些条件的检查 而不必使用 try catch 并且我想避免出现 Index Out of Range 错误的可能性 if array Element 0 Object Length gt 0 array Element 1 Ob

随机推荐

  • 完成该操作所需的数据还不可使用

    原因是没有加下面两个判断条件 if xmlhttp readyState 4 if xmlhttp status 200
  • 【目标检测】从头到尾教你安装MMDetection(超详细版本)

    目录 MMDetection的安装过程 前言 一 本地环境 二 先决条件 1 从官方网站下载并安装Anaconda 2 创建 conda 环境并激活它 3 按照官方说明安装 PyTorch 例如 三 配置PyTorch环境时出现的第一个错误
  • vue引入elementUI部分组件库

    package json中加入 babel plugin component 1 1 1 借助 babel plugin component 我们可以只引入需要的组件 以达到减小项目体积的目的 如果你 只希望引入部分组件 比如 Button
  • AI学习_过拟合的细节,及其解决方法【未完成】

    要标准化 归一化的原因 把数据保留在 1 1之间 防止数值太大 发生梯度弥散 什么时候用标准化 什么时候用归一化 连续数据就用标准化 ps 但0不代表 大小 时 就不能用标准化了 BN的含义 标准化的意义 是统一量纲 BN其实是在nchw中
  • 小皮面板开启apache服务错误(主要是80端口被占用)

    在小皮面板中开启apache时出现这样的报错 98 Address already in use AH00072 make sock could not bind to address 80 98 Address already in us
  • 富士施乐3065扫描教程_富士施乐怎么设置扫描到PC

    展开全部 1 将复印机的IP输入在IE的地址栏里 32313133353236313431303231363533e59b9ee7ad9431333365666232用户名是11111 密码是x admin 进去以后找到协议下的9100项
  • Axure Share ——原型设计工具 Axure ,移动版

    什么是Axure Share Axure Share 是老牌原型设计工具Axure 的移动版 app 支持 iOS iPhone iPad 以及 Android 设备 我们可以使用它来查看和演示通过 Axure 制作的移动 app 原型 A
  • Vuex之理解mutation的用法

    一 什么是mutation 通俗的理解mutations 里面装着一些改变数据方法的集合 这是Veux设计很重要的一点 就是把处理数据逻辑方法全部放在mutations里面 使得数据和视图分离 切记 Vuex中store数据改变的唯一方法就
  • D13 LeetCode 599.两个列表的最小索引和(简单)

    一 题目 二 思路 自己 先遍历两个数组 找出元素值相等的元素同时记录下标和 这时候我想到了要用到map 但是map不允许键值重复 我就一直在纠结怎么不让他更新或者记录相等键的元素值 然后想破了头也没想清楚 最后想着用list来记录 把 下
  • Vue router-view 路由无缝切换动画

    Vue router view 路由无缝切换动画 左滑淡出 右滑淡入 HTML div class wrap div
  • android面试内存管理,Android面试之内存优化

    内存泄漏 用动态存储分配函数动态开辟的空间 在使用完毕后未释放 结果导致一直占据该内存单元 直到程序结束 即所谓的内存泄漏 内存泄漏是造成应用程序OOM 内存溢出 的主要原因之一 怎样避免内存泄漏 1 单例模式引发的内存泄漏 单例模式里的静
  • 华为OD机试 - 连续字母长度(Java)

    题目描述 给定一个字符串 只包含大写字母 求在包含同一字母的子串中 长度第 k 长的子串的长度 相同字母只取最长的那个子串 输入描述 第一行有一个子串 1 lt 长度 lt 100 只包含大写字母 第二行为 k的值 输出描述 输出连续出现次
  • 神经网络训练

    在数码管识别中 识别之前 字符归一化之后的大小是20 20个像素
  • 听说Python多线程和多进程有鸡肋?一起聊聊...

    听说是鸡肋 一直以来 关于Python的多线程和多进程是否是鸡肋的争议一直存在 今晚抽空谈谈我的看法 以下是我的观点 对于多线程 Python 的多线程库 threading 在某些情况下确实是鸡肋的 这是因为 Python 的全局解释器锁
  • CentOS7.X版本下安装MySQL5.7

    记录CentOS7 X版本下安装MySQL5 7数据库 设置rpm下载目录在 opt目录下新建一个目录存放mysql cd opt sudo mkdir mysql 下载MySQL的源 wget http repo mysql com my
  • [CTF/网络安全] 攻防世界 disabled_button 解题详析

    CTF 网络安全 攻防世界 disabled button 解题详析 input标签 姿势 disable属性 总结 题目描述 X老师今天上课讲了前端知识 然后给了大家一个不能按的按钮 小宁惊奇地发现这个按钮按不下去 到底怎么才能按下去呢
  • Centos7.4安装kvm虚拟机(使用virt-manager管理)

    原文链接 https www centos bz 2018 02 centos7 4 E5 AE 89 E8 A3 85kvm E8 99 9A E6 8B 9F E6 9C BA EF BC 88 E4 BD BF E7 94 A8vir
  • 2022年SQL经典面试题总结(带解析)

    一 选择题 1 基础题 1 要求删除商品表中价格大于3000的商品 下列SQL语句正确的是 A DELETE FROM 商品 WHERE 价格 gt 3000 B DELETE FROM 商品 WHERE 价格 gt 3000 C DELE
  • 【空间面板计量专题,举一反三,学通学透】

    重点内容 空间计量概念 空间权重矩阵 空间面板计量全套代码 前言 最近因为要写一篇关于环境规制的论文 需要用到空间计量的方法 于是开始从零学习这个模块的内容 在耗费大量精力以及微薄的财力之后 最终也是在实际操作方面能够得以初窥门径 不过回顾
  • 【模板】树状数组

    文章目录 1 概述 2 原理 3 实现 3 1 lowbit x 3 2 查询前缀和 3 3 单点增加 4 初始化 1 概述 树状数组 Binary Indexed Trees 其基本用途是维护序列的前缀和 对于给定的序列 a a