动态前缀和数组:树状数组

前缀和的不足

前缀和是一种常见的算法思想,能够实现在常数时间复杂度下得到某个子区间内所有元素和。以一维数组 nums 为例,定义前缀和数组 preSum,preSum[i] 表示 nums 前 i 个元素的和,利用动态规划的思想,易得 preSum[i] = preSum[i - 1] + nums[i] 的递推关系,因此构造一个前缀和数组的时间复杂度为 O(n),而查询前 i 个元素的和只需查询 preSum[i] 的值,为常数时间。

前缀和方法在数组元素不发生改变的情况下十分高效,但如果数组元素可能会发生改变,与朴素求和做法(不使用前缀和数组,而是直接遍历区间元素累计求和)相比,前缀和数组需要 O(n) 的时间来进行更新。这两种做法要么查询是 O(1)、更新是 O(n),要么查询是 O(n)、更新是 O(1),那有没有一种折衷的方案,使得查询和更新效率都不至于太低呢?本文将介绍的树状数组就符合这样的条件。

树状数组

查询

由正整数的二进制表示可知,任何一个正整数都可以拆分为为若干个不重复的 2 的幂之和。那么对于一个下标从 1 开始且长度为 n 的数组,它的任意下标 i (1 <= i < n) 也可以依照此方案进行拆分,例如 7 = 4 + 2 + 1,那么对于一个区间 [1 ~ 7],令被拆分得到的各整数为区间长度,按照从大到小的顺序,依次从左到右对区间进行分割,得到的各子区间为 [1 ~ 4]、[5 ~ 6] 和 [7 ~ 7]。这样分割具备一个非常好的性质:

对于分割后得到的任何子区间 [l, r],r 必定唯一,且 r 的个数正好等于 n.

也就是说不存在两个子区间 [l1, r1]、[l2, r2] 满足:r1 = r2 且 l1 ≠ l2. 那么就可以以 r 为关键字(下标),构造一个数组 tree,tree[r] 表示区间 [l, r] (若 r 确定,则 l 也确定)的元素和。那么根据 7 = 4 + 2 + 1,有 preSum[7] = tree[4] + tree[6] + tree[7]。如下图所示,图中给出了对下标 1 ~ 16 进行拆分的结果。

请添加图片描述

由于任何一个正整数 i 拆分后的整数数为其二进制表示中 1 的个数,令该个数为 ns,对于区间 [1, i],其拆分得到区间个数也为 ns,即 preSum[i] 最多由 ns 个 tree 数组元素累加得到,因此前缀和的查询效率为 O(logn).

更新

上图中的连接线代表了 nums 数组元素(黄色方格)和 tree 数组元素(蓝色方格)以及不同 tree 数组元素之间的相互依赖关系,若 nums 数组元素发生改变,便需要根据上述依赖关系自底向上对 tree 数组元素进行更新,以保证查询的正确性,问题就在于如何用规范的数学语言表示图中所示的依赖关系。

既然区间的分割主要基于二进制的位级表示,那么元素更新的依赖关系也不妨从二进制的角度出发。首先观察下标 9 的更新路径:9 -> 10 -> 12 -> 16,其二进制表示分别为:

 9: 01001
10: 01010
12: 01100
16: 10000

似乎有 10 = 9 + 1,12 = 10 + 2,16 = 12 + 4 的关系存在。其中下标每次增加的值都为 2 的幂,且该 2 的幂即为当前下标 i 按上述规则拆分后得到的最小的数字。事实也的确如此(具备数学证明可以参考带你发明树状数组!附数学证明),这个最小数字通常称为一个数的 lowbit,即 lowbit[9] = 1lowbit[10] = 2lowbit[12] = 4

得到这个规律后,更新操作便很容易了:若 nums[i] 改变,则首先更新 tree[i],然后 i += lowbit(i),继续更新 tree[i],直到 i 超出了数组的范围,更新结束。注意到,lowbit[i] 在更新过程中是不断增大的,因此更新次数最多不超过 logn 次,即 tree 数组的更新效率为 O(logn).

构造

了解了如何根据 tree 数组计算前缀和以及如何更新 tree 数组后,接下来的问题就是如何初始化 tree 数组的值。一个简单的做法是先将 tree 数组各元素初始化为 0,再依次对每个 nums[i] 执行更新操作,这种方法的时间复杂度为 O(nlogn)。

注意 tree 数组的下标代表分割区间的右端点位置,如果当前更新到了下标 i 的位置,那么说明 tree[i] 的值已经初始化完毕(nums[j](j > i)tree[i] 无关),因此可直接将该值加入 tree[i + lowbit(i)] 中。

代码实现

class binaryIndexedTree {
private:
	vector<int> tree;
	vector<int> nums;
public:
	binaryIndexedTree(vector<int>& nums) : nums(nums), tree(nums.size() + 1) {
		for (int i = 1; i <= nums.size(); ++i) {
			tree[i] += nums[i - 1];
			int next = i + (i & -i); // i & -i 即为 lowbit(i)
			if (next <= nums.size()) {
				tree[next] += tree[i];
			}
		}
	}
	void update(int idx, int val) {
		int dv = val - nums[idx];
		nums[idx] = val;
		for (int i = idx + 1; i < tree.size(); i += i & -i) {
			tree[i] += dv;
		}
	}
	int preSum(int idx) {  // 区间 nums[0~idx) 的和
		int sum = 0;
		while (idx > 0) {
			sum += tree[idx];
			idx -= (idx & -idx);
		}
		return sum;
	}
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣128. 最长连续序列(哈希表)

Problem: 128. 最长连续序列 文章目录 题目描述思路复杂度Code 题目描述 思路 1.先将数组中的元素存入到一个set集合中&#xff08;去除重复的元素&#xff09; 2.欲找出最长连续序列&#xff08;先定义两个int变量longestSequence和currentSequence用于记录最长连续序列和当前…

HTML5:七天学会基础动画网页7

CSS3高级特效 2D转换方法 移动:translate() 旋转:rotate() 缩放:scale() 倾斜:skew() 属性:transform 作用:对元素进行移动,旋转,缩放,倾斜。 2D移动 设定元素从当前位置移动到给定位置(x,y) 方法 说明 translate(x,y) 2D转换 沿X轴和Y轴移…

【Python】OpenCV-使用ResNet50进行图像分类

使用ResNet50进行图像分类 如何使用ResNet50模型对图像进行分类。 import os import cv2 import numpy as np from tensorflow.keras.applications.resnet50 import ResNet50, preprocess_input, decode_predictions from tensorflow.keras.preprocessing import image# 设置…

【计算机网络】IO多路转接之poll

文章目录 一、poll函数接口二、socket就绪条件三、poll的优点四、poll的缺点五、poll使用案例--只读取数据的server服务器1.err.hpp2.log.hpp3.sock.hpp4.pollServer.hpp5.main.cc 一、poll函数接口 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int t…

EmoLLM(心理健康大模型)——探索心灵的深海,用智能的语言照亮情感的迷雾。

文章目录 介绍&#xff1a;应用地址&#xff1a;模型地址&#xff1a;Github地址&#xff1a;视频介绍&#xff1a;效果图&#xff1a; 介绍&#xff1a; EmoLLM是一个基于 InternLM 等模型微调的心理健康大模型&#xff0c;它涵盖了认知、情感、行为、社会环境、生理健康、心…

Python绘制不同形状词云图

目录 1.基本词云图1.1 导入所需库1.2 准备词汇1.3 配置参数并生成词云图1.4 在Python窗口中显示图片1.5 效果展示1.6 完整代码 2. 不同形状词云图2.1 找到自己所需形状图片2.2 利用PS将图片设置为黑白色2.3 在代码中设置背景2.4 效果展示 1.基本词云图 1.1 导入所需库 import…

2023全球软件开发大会-上海站:探索技术前沿,共筑未来软件生态(附大会核心PPT下载)

随着信息技术的迅猛发展&#xff0c;全球软件开发大会&#xff08;QCon&#xff09;已成为软件行业最具影响力的年度盛会之一。2023年&#xff0c;QCon再次来到上海&#xff0c;汇聚了众多业界精英、技术领袖和开发者&#xff0c;共同探讨软件开发的最新趋势和实践。 一、大会…

IO接口 2月5日学习笔记

1.fgetc 用于从文件中读取一个字符&#xff0c;fgetc 函数每次调用将会返回当前文件指针所指向的字符&#xff0c;并将文件指针指向下一个字符。 int fgetc(FILE *stream); 功能: 从流中读取下一个字符 参数: stream:文件流指针 返回值: …

嵌入式驱动学习第二周——断言机制

前言 这篇博客来聊一聊C/C的断言机制。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程&#xff0c;未来预计四个月将高强度更新本专栏&#xff0c;喜欢的可以关注本博主并订阅本专栏&#xff0c;一起讨论一起学习。现在关注就是老粉啦&#xff01; 目录 前言1. 断言介绍…

鸿蒙实战应用开发:【拨打电话】功能

概述 本示例通过输入电话&#xff0c;进行电话拨打&#xff0c;及电话相关信息的显示。 样例展示 涉及OpenHarmony技术特性 网络通信 基础信息 拨打电话 介绍 本示例使用call相关接口实现了拨打电话并显示电话相关信息的功能 效果预览 使用说明 1.输入电话号码后&#…

枚举与尺取法(蓝桥杯 c++ 模板 题目 代码 注解)

目录 组合型枚举&#xff08;排列组合模板&#xff08;&#xff09;&#xff09;: 排列型枚举&#xff08;全排列&#xff09;模板&#xff1a; 题目一&#xff08;公平抽签 排列组合&#xff09;&#xff1a; ​编辑 代码&#xff1a; 题目二&#xff08;座次问题 全排…

寒假作业Day 06

寒假作业Day 06 一、选择题 1、关于内存管理&#xff0c;以下有误的是&#xff08; &#xff09; A: malloc在分配内存空间大小的时候是以字节为单位 B: 如果原有空间地址后面还有足够的空闲空间用来分配&#xff0c;则在原有空间后直接增加新的空间&#xff0c;使得增加新空…

No matching version found for @babel/traverse@^7.24.0.

问题&#xff1a; npm安装 依赖失败&#xff0c;找不到所需依赖。 原因&#xff1a; npm镜像源中没有该依赖。&#xff08;大概率是因为依赖最近刚更新&#xff0c;当前镜像源没有同步&#xff09; 解决&#xff1a; 查看自己的npm镜像&#xff1a;npm config get registry…

【刷题记录】详谈设计循环队列

下题目为个人的刷题记录&#xff0c;在本节博客中我将详细谈论设计循环队列的思路&#xff0c;并给出代码&#xff0c;有需要借鉴即可。 题目&#xff1a;LINK 下面是思路分析: 首先一开始收到实现普通队列的思路影响上题目中循环这俩字&#xff0c;那自然想到的是用链表来设计…

IDEA自动导入provided的依赖

最近在学习flink 流程序&#xff0c;在写demo程序的时候依赖flink依赖&#xff0c;依赖的包在flink集群里面是自己已经提供了的&#xff0c;在导入的时候配置为provided&#xff0c;像下面这样&#xff0c;以使打包的时候不用打到最终的程序包里面。 <dependency><gro…

带你从Spark官网啃透Spark Structured Streaming

By 远方时光原创&#xff0c;可转载&#xff0c;open 合作微信公众号&#xff1a;大数据左右手 本文是基于spark官网结构化流解读 Structured Streaming Programming Guide - Spark 3.5.1 Documentation (apache.org) spark官网对结构化流解释 我浓缩了一些关键信息&#xff…

laravel8配合jwt

composer 安装包 composer require tymon/jwt-authconfig/app.php 注册服务提供者 providers > [Tymon\JWTAuth\Providers\LaravelServiceProvider::class, ]aliases > [JWTAuth > Tymon\JWTAuth\Facades\JWTAuth::class,JWTFactory > Tymon\JWTAuth\Facades\JWT…

如何把已安装的nodejs高版本降级为低版本

第一步.先清空本地安装的node.js版本 按健winR弹出窗口&#xff0c;键盘输入cmd,然后敲回车&#xff08;或者鼠标直接点击电脑桌面最左下角的win窗口图标弹出&#xff0c;输入cmd再点击回车键&#xff09; 然后进入命令控制行窗口&#xff0c;并输入where node查看之前本地安装…

[java] 23种设计模式之桥接模式

一、什么是桥接模式 桥接(Bridge)模式属于结构型设计模式。通过提供抽象化和实现化之间的桥接结构&#xff0c;来实现二者的解耦。把抽象(abstraction)与行为实现(implementation)分离开来&#xff0c;从而可以保持各部分的独立性以及应对它们的功能扩展。 二、适用场景 当一…

《互联网的世界》第四讲-拥塞控制与编码

需要澄清的一个误区是&#xff0c;拥塞绝不是发送的数据量太大导致&#xff0c;而是数据在极短的时间段内到达了同一个地方以至于超过了网络处理容量导致&#xff0c;拥塞的成因一定要考虑时间因素。换句话说&#xff0c;拥塞由大突发导致。 只要 pacing&#xff0c;再多的数据…