【模板】树状数组

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(使用前将#替换为@)

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

随机推荐

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

    原因是没有加下面两个判断条件 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