树状数组(代码模板和原理详解)

news2024/11/13 12:38:13

树状数组代码模板

普通数组:求前缀和: O ( n ) O(n) O(n),修改: O ( 1 ) O(1) O(1)

前缀和数组:求前缀和: O ( 1 ) O(1) O(1),修改: O ( n ) O(n) O(n)

鱼和熊掌不可兼得,当我们同时需要对一个数组求前缀和和修改时,这两种数组的时间复杂度都比较高。

而树状数组是一个折中的方案,它运用二进制优化,求前缀和和修改操作的时间复杂度都是 O ( log ⁡ n ) O(\log n) O(logn)

下面是代码模板:

  • tr 树状数组,下标从 1 开始
  • add 在下标为 x 的位置加 c
  • sum 求下标 [ 1 , x ] [1,x] [1,x] 的元素的和
const int N = 100010;

int n;
int tr[N];

int lowbit(int x) {
    return x & -x;
}

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

int sum(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

因为代码比较简短,所以你可以直接把它背下来,使用的时候 tr 数组就当成普通的数组看待,但是只能通过 addsum 函数对它进行操作

原理

lowbit 函数

lowbit 函数用来求一个数的二进制表示的最低一位 1 1 1 所表示的数。如 l o w b i t ( 6 ) = 2 lowbit(6) = 2 lowbit(6)=2 6 6 6 的二进制为 110 110 110,最低一位 1 1 1 就是 10 10 10 即为 2 2 2

技巧是
x   &   − x \rm x\ \&\ -x x & x
负数在计算机中存储的是补码,补码就是一个数的二进制按位取反然后加 1 1 1 − x -x x 在计算机中的存储就是将 x x x 取反再加 1 1 1

因此获取 x x x 最低一位 1 1 1 的原理如下:

  • 假设 x x x 的二进制表示为 ( a 10 ⋯ 0 ) 2 (a10\cdots0)_2 (a100)2,其中 a a a 表示若干个高位, 1 1 1 表示最低位的那个 1 1 1 0 ⋯ 0 0\cdots0 00 表示后面的若干个 0 0 0,那么 − x -x x 的二进制表示为

  • ( a ‾ 01 ⋯ 1 ) 2 + ( 1 ) 2 = ( a ‾ 10 ⋯ 0 ) 2 (\overline{a}01\cdots1)_2+(1)_2=(\overline{a}10\cdots0)_2 (a011)2+(1)2=(a100)2

    其中 a ‾ \overline{a} a 表示高位的 a a a 按位取反。

  • 最后 ( a ‾ 10 ⋯ 0 ) 2   &   ( a 10 ⋯ 0 ) 2 = ( 10 ⋯ 0 ) 2 (\overline{a}10\cdots0)_2\ \&\ (a10\cdots0)_2 = (10\cdots0)_2 (a100)2 & (a100)2=(100)2,即求出了最低一位 1 1 1


树状数组则是这样一种思路:

如果我们要求前 ( 11010 ) 2 (11010)_2 (11010)2 项的和,可以先求前 ( 10000 ) 2 (10000)_2 (10000)2 项的和,再求接下来 ( 1000 ) 2 (1000)_2 (1000)2 项的和,最后求接下来 ( 10 ) 2 (10)_2 (10)2 项的和,然后把这三个和相加,就是我们要求的答案了。因为只要枚举每一位 1 1 1,所以时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

根据这个思路,我们给出两个定义:

  • 设原数组为 a [ ] \rm a[] a[]

  • t r [ i ] \rm tr[i] tr[i] 即为原数组中,以下标 i i i 结尾的长度为 l o w b i t ( i ) \rm lowbit(i) lowbit(i) 的后缀子数组的和,即下标 [ i − l o w b i t ( i ) + 1 , i ] [i-{\rm lowbit}(i)+1,i] [ilowbit(i)+1,i] 的范围。

这样 t r [ ( 11010 ) 2 ] \rm tr[(11010)_2] tr[(11010)2] 就是原数组中以下标 ( 11010 ) 2 (11010)_2 (11010)2 为结尾的 ( 10 ) 2 (10)_2 (10)2 个后缀元素的和,接下来让 ( 11010 ) 2 − ( 10 ) 2 = ( 11000 ) 2 (11010)_2-(10)_2=(11000)_2 (11010)2(10)2=(11000)2 t r [ ( 11000 ) 2 ] \rm tr[(11000)_2] tr[(11000)2] 就是以下标 ( 11000 ) 2 (11000)_2 (11000)2 为结尾的 ( 1000 ) 2 (1000)_2 (1000)2 个后缀元素的和,以此类推, ( 11000 ) 2 − ( 1000 ) 2 = ( 10000 ) 2 (11000)_2-(1000)_2=(10000)_2 (11000)2(1000)2=(10000)2 t r [ ( 10000 ) 2 ] \rm tr[(10000)_2] tr[(10000)2] 就是以下标 ( 10000 ) 2 (10000)_2 (10000)2 为结尾的 ( 10000 ) 2 (10000)_2 (10000)2 个后缀元素的和。显然这三个和是不重不漏的,相加即为前 ( 11010 ) 2 (11010)_2 (11010)2 项和。

如图:

可以看到其实是一个树状结构,箭头表示一种包含关系

img

查询前缀和的方法上面已经讲过了,下面我们思考如何进行修改。

要修改原数组 a \rm a a 中某个元素的值,比如修改 a [ 6 ] \rm a[6] a[6] ,从图中来看,显然它会影响到 t r [ 6 ] 、 t r [ 8 ] 、 t r [ 16 ] \rm tr[6]、tr[8]、tr[16] tr[6]tr[8]tr[16] 。也就是要更新从叶子结点 6 到根结点的这条路径。

那么,如何从子结点找父结点?

假设父结点 p = ( a 10 ⋯ 0 ) 2 p = (a10\cdots0)_2 p=(a100)2,那么其子节点为了保证包含于它,也就是,大小比 p p p 小,子数组的长度也比 p p p 短,则子结点 i i i 一定是 ( a 01 ⋯ 10 ⋯ 0 ) 2 (a01\cdots10\cdots0)_2 (a01100)2 的形式,所以我们只要对子节点 i i i 加上一个 l o w b i t ( i ) \rm lowbit(i) lowbit(i) 就可以得到父结点。


最后我们提一下树状数组的建树方式:

给定我们一个数组,让我们对其进行建树。

一、

最直接也最常用方式就是使用 add 函数

for (int i = 1; i <= n; ++i) add(i, a[i]);

时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

二、

算每条边

t r [ 12 ] = t r [ 10 ] + t r [ 11 ] + a [ 12 ] \rm tr[12] = tr[10] + tr[11] + a[12] tr[12]=tr[10]+tr[11]+a[12]

for (int x = 1; x <= n; ++x) {
    for (int i = x - 1; i >= x - lowbit(x) + 1; i -= lowbit(i)) {
         tr[x] += tr[i];
    }
}

时间复杂度为 O ( n ) O(n) O(n)

三、

直接对原数组求前缀和,然后根据 t r \rm tr tr 数组的定义进行建树

for (int i = 1; i <= n; ++i) {
    a[i] += a[i - 1];
    tr[i] = a[i] - a[i - lowbit(i)];
}

时间复杂度为 O ( n ) O(n) O(n)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/177731.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NEZUKO: 1——202201152003

NEZUKO: 1——202201152003 About Release Back to the Top Name: nezuko: 1Date release: 21 Aug 2019Author: yunaranyancatSeries: nezuko Download Back to the Top Please remember that VulnHub is a free community resource so we are unable to check the machin…

在Java中使用堆排序求解TopK问题

在Java中使用堆排序求解TopK问题 1. 问题描述 给定一个很大的数组&#xff0c;长度100w&#xff0c;求第k大的数是多少&#xff1f; 这个问题是一个很经典的问题&#xff0c;如果采用传统方式&#xff0c;即现排序&#xff0c;然后找到第k个数&#xff0c;对于数据量很大的时…

Knowledge-based-BERT(一)

多种预训练任务解决NLP处理SMILES的多种弊端&#xff0c;代码&#xff1a;Knowledge-based-BERT&#xff0c;原文&#xff1a;Knowledge-based BERT: a method to extract molecular features like computational chemists&#xff0c;代码解析从K_BERT_pretrain开始。模型框架…

Tkinter的Listbox控件

Tkinter的Listbox控件是个选项框&#xff0c;主要是用来在给定的选项中选择一个 使用方法 创建选项框Listbox 和其他控件的创建方法一样&#xff0c;直接创建即可&#xff0c;命名为Lb Lbtk.Listbox(root) Lb.pack() 在选项框中加入选项 可以边创建边添加&#xff0c;即利…

【C#】WPF实现经典纸牌游戏,适合新手入门

文章目录1 纸牌类2 布局3 初始化4 事件点击牌堆拖动牌的去留源代码1 纸牌类 之所以产生这个无聊至极的念头&#xff0c;是因为发现Unicode中竟然有这种字符。。。 黑桃&#x1f0a1; &#x1f0a2; &#x1f0a3; &#x1f0a4; &#x1f0a5; &#x1f0a6; &#x1f0a7; &…

【设计模式】结构型模式·外观模式

学习汇总入口【23种设计模式】学习汇总(数万字讲解体系思维导图) 写作不易&#xff0c;如果您觉得写的不错&#xff0c;欢迎给博主来一波点赞、收藏~让博主更有动力吧&#xff01;> 学习汇总入口 一.概述 外观&#xff08;Facade&#xff09;模式是七大设计原则“迪米特法则…

谷粒商城-高级篇-Day12-性能压测和缓存

文章目录性能优化nginx动静分离优化三级分类的获取&#xff08;优化业务&#xff09;分布式缓存整合redis高并发下的缓存失效问题缓存穿透缓存雪崩缓存击穿解决这些问题分布式锁Redisson可重入锁&#xff08;Reentrant Lock&#xff09;指定过期时间读写锁闭锁信号量使用Redssi…

Python实现一个简易的CLI翻译程序

Python实现一个简易的CLI翻译程序Python百度翻译API实现一个简易的CLI翻译程序获取百度翻译API编写一个简单的Python程序Python百度翻译API实现一个简易的CLI翻译程序 之前翻译用的linux上的golddict,每次翻译都很慢。。。 所以想写一个简单快速的翻译命令行翻译软件 获取百度…

Allegro如何自动高亮不等长的网络操作指导

Allegro如何自动高亮不等长的网络操作指导 在做PCB设计的时候,时常需要要做等长,Allegro可以自动高亮一组内不等长的网络,可以直观的看到哪些网络长度是不满足的,类似下图 绿色的是通过的,红色是长度不足的,粉色是超长的 具体操作如下 选择Route-Timing Vision出现optio…

Springboot359的医院病历管理系统

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 2 第3章 系统分析 3 3.1 需求分析 3 3.2 系统可行性分析 4 3.2.1技术可行性&#xff1a;技术背景 4 3.2.2经济…

Ubiquiti MAC Address Changer 3.0 Crack

Ubiquiti MAC Address Changer&#xff0c;目前mac address changer的版本有很多&#xff0c;本次发布的是V3版本&#xff0c;这是一款功能非常强大的修改网卡mac地址软件&#xff0c;基本上所有的网卡MAC地址都支持修改&#xff0c;包括虚拟机和TeamViewer软件都是支持的。 Ea…

5、基本数据类型

目录 一、整数类型 二、浮点类型 三、字符类型 四、布尔类型 一、整数类型 整数类型用来存储整数数值&#xff0c;即没有小数部分的数值。可以是正数&#xff0c;也可以是负数。整 型数据在Java程序中有3种表示形式&#xff0c;分别为十进制、八进制和十六进制。 1.十进…

2.4.4 数值类型的转换

文章目录1.运算时的自转2.运算时的强转3.强转时的精度丢失问题1.运算时的自转 不同数字类型之间的大小关系如下&#xff1a;double > float > long > int > char, short,byte 自转&#xff1a;小类型的数据可以直接赋值给大类型的变量&#xff1b; byte short c…

Linux(五)创建一个miniShell

前情提要&#xff1a;掌握进程控制中的进程创建、进程终止、进程等待、进程替换。可以参考下方博文 LInux&#xff08;四&#xff09;进程控制&#xff08;创建、终止、等待、替换&#xff09; 了解strtok函数的使用 正文&#xff1a; 目录 Shell是什么&#xff1f; 如何…

蓝桥杯之二分与前缀和

蓝桥杯之二分二分板子&#xff1f;第一次和最后一次出现的位置机器人跳跃问题四平方和分巧克力&#xff1f;典型二分找大的&#xff08;从右往左找&#xff09;二分upper_bound(a1,an1,x)-a&#xff1f;递增三元组前缀和取余&#xff1f;K倍区间二维前缀和&#xff1f;激光炸弹…

17种编程语言实现排序算法-合并排序

开源地址 https://gitee.com/lblbc/simple-works/tree/master/sort/ 覆盖语言&#xff1a;C、C、C#、Java、Kotlin、Dart、Go、JavaScript(JS)、TypeScript(TS)、ArkTS、swift、PHP。 覆盖平台&#xff1a;安卓(Java、Kotlin)、iOS(SwiftUI)、Flutter(Dart)、Window桌面(C#)、…

分享139个ASP源码,总有一款适合您

ASP源码 分享139个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 139个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1Vk4U4EXVCWZWPMWf9ax2dw?pwdif23 提取码&#x…

【C++】类和对象(上)---什么是类?

目录1.面向过程和面向对象初步认识2.类的引入2.1使用struct定义类3.类的定义3.1类的两种定义方式&#xff1a;3.2成员变量命名规则的建议3.3成员函数与成员变量定义的位置建议4.类的访问限定符及封装4.1访问限定符4.2封装5.类的作用域6.类的实例化7.类对象模型7.1如何计算类对象…

springboot静态资源目录访问,及自定义静态资源路径,index页面的访问

springboot静态资源目录访问&#xff0c;及自定义静态资源路径&#xff0c;index页面的访问静态资源目录的访问位置静态资源访问测试自定义静态资源路径和静态资源请求映射web首页的访问自定义静态资源请求映射影响index.html首页的访问的**解决方案**&#xff1a;1.取消自定义…

【JUC系列】CountDownLatch实现原理

简单示例 public class Main {private static final int NUM 3;public static void main(String[] args) throws InterruptedException {CountDownLatch latch new CountDownLatch(NUM);for (int i 0; i < NUM; i) {new Thread(() -> {try {Thread.sleep(2000);Syste…