进阶数据结构——树状数组
前言
看这篇文章前我建议你们先看这个视频还有这个视频,不然你们可能看不懂。

一、树状数组的核心思想与本质
核心思想:树状数组(Fenwick Tree)是一种用于高效处理前缀和查询和单点更新的数据结构。
本质:通过二进制索引和树形结构,将前缀和的计算复杂度从
O(n) 优化到 O(logn)。
二、树状数组的基本操作
-
初始化
初始化一个大小为 n 的数组,所有元素初始值为 0。 -
单点更新
更新某个位置的值,并维护树状数组的结构。 -
前缀和查询
查询从第一个元素到某个位置的前缀和。 -
区间和查询
查询某个区间的和。
三、树状数组的应用场景
动态前缀和查询:
实时查询数组的前缀和,支持单点更新。
示例:LeetCode 307. 区域和检索 - 数组可修改。
逆序对计数:
使用树状数组统计数组中逆序对的数量。
示例:LeetCode 493. 翻转对。
区间更新与单点查询:
通过差分数组和树状数组实现区间更新和单点查询。
示例:LeetCode 370. 区间加法。
二维树状数组:
处理二维数组的前缀和查询和单点更新。
示例:LeetCode 308. 二维区域和检索 - 可变。
四、树状数组的复杂度分析
时间复杂度
单点更新:
O(logn)。
前缀和查询:
O(logn)。
区间和查询:
O(logn)。
空间复杂度
存储树状数组:
O(n)。
五、树状数组的优化技巧
-
差分数组
通过差分数组实现区间更新和单点查询。 -
离散化
在数据范围较大时,使用离散化减少树状数组的大小。 -
多维扩展
将树状数组扩展到二维或更高维度,处理多维数组的前缀和查询和单点更新。
六、常见误区与调试技巧
-
误区一:树状数组适用于所有区间查询问题
澄清:树状数组适用于前缀和查询和单点更新,对于复杂的区间查询问题可能需要其他数据结构(如线段树)。 -
误区二:树状数组的初始化复杂度高
澄清:树状数组的初始化复杂度为 O(nlogn),但可以通过线性初始化优化到 O(n)。 -
调试方法
打印树状数组:在每次操作后输出树状数组的内容,检查是否正确。
可视化树结构:手动画出树状数组的结构,模拟操作过程。
七、代码模版
单点更新
例题 【模板】树状数组 1
#include<iostream>
#include<vector>
using namespace std;template<class T>
class FenwickTree {
private:vector<T>m_tree;int lowbit(int x);
public:FenwickTree(int n):m_tree(n+1,0){}void update(int idx,T val);T query(int idx);T query(int l, int r);
};template<class T>
int FenwickTree<T>::lowbit(int x) {return x & (-x);
}template<class T>
void FenwickTree<T>::update(int idx, T val) {int n = (int)m_tree.size();while (idx < n) {m_tree[idx] += val;idx += lowbit(idx);}
}template<class T>
T FenwickTree<T>::query(int idx) {T sum = 0;while (idx > 0) {sum += m_tree[idx];idx -= lowbit(idx);}return sum;
}template<class T>
T FenwickTree<T>::query(int l, int r) {return query(r) - query(l - 1);
}int main() {int n, m;cin >> n >> m;FenwickTree<int> ft(n);for (int i = 1; i <= n; i++) {int x;cin >> x;ft.update(i, x);}while (m--) {int z, x, y;cin >> z >> x >> y;if (z == 1) {ft.update(x, y);}else {int sum = ft.query(x, y);cout << sum << endl;}}return 0;
}
区间更新
例题 【模板】树状数组
#include<iostream>
#include<vector>
using namespace std;template<class T>
class FenwickTree {
private:vector<T>m_tree;int lowBit(int idx);void update(int idx, T val);T query(int idx);T query(int l, int r);
public:FenwickTree(int n):m_tree(n+2,0){}void updateInterval(int l, int r, T val);T queryIndex(int idx);
};template<class T>
int FenwickTree<T>::lowBit(int idx) {return idx & -idx;
}template<class T>
void FenwickTree<T>::update(int idx, T val) {int n = (int)m_tree.size();while (idx < n) {m_tree[idx] += val;idx += lowBit(idx);}
}template<class T>
T FenwickTree<T>::query(int idx) {T sum = 0;while (idx > 0) {sum += m_tree[idx];idx -= lowBit(idx);}return sum;
}template<class T>
T FenwickTree<T>::query(int l, int r) {return query(r) - query(l - 1);
}template<class T>
void FenwickTree<T>::updateInterval(int l, int r, T val) {update(l, val);update(r + 1, -val);
}template<class T>
T FenwickTree<T>::queryIndex(int idx) {return query(idx);
}int main() {int n, m;cin >> n >> m;FenwickTree<int>ft(n);for (int i = 1; i <= n; i++) {int a;cin >> a;ft.updateInterval(i, i, a);}while (m--) {int opt;cin >> opt;if (opt == 1) {int l, r, x;cin >> l >> r >> x;ft.updateInterval(l, r, x);}else {int k;cin >> k;cout << ft.queryIndex(k) << endl;}}return 0;
}
八、经典例题
排序
#include<iostream>
#include<vector>
using namespace std;template<class T>
class FenwickTree {
private:vector<T>m_tree;int lowBit(int idx);
public:FenwickTree(int n):m_tree(n+1,0){}void update(int idx, T val);T query(int idx);T query(int l, int r);
};template<class T>
int FenwickTree<T>::lowBit(int idx) {return idx & -idx;
}template<class T>
void FenwickTree<T>::update(int idx, T val){int n = m_tree.size();while (idx < n) {m_tree[idx] += val;idx += lowBit(idx);}
}template<class T>
T FenwickTree<T>::query(int idx) {T sum = 0;while (idx > 0) {sum += m_tree[idx];idx -= lowBit(idx);}return sum;
}template<class T>
T FenwickTree<T>::query(int l, int r) {return query(r) - query(l - 1);
}#define maxn 1000001
int a[maxn];int main() {int n, m;cin >> n;FenwickTree<long long>ft(maxn);for (int i = 0; i < n; i++) {cin >> a[i];}long long sum = 0;for (int i = n - 1; i >= 0; i--) { //逆序遍历数组看看后面有多少个比当前这个值小的个数,乘于它本身累加起来ft.update(a[i], 1);sum += (long long)ft.query(a[i] - 1) * a[i]; //求1~a[i]-1元素个数}cout << sum << endl;return 0;
}
逆序对的数量
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;template<class T>
class Dicretizer {
private:vector<T>m_data;
public:void addData(T v);void process();int get(T v) const;int size() const;
};template<class T>
void Dicretizer<T>::addData(T v) {m_data.push_back(v);
}template<class T>
void Dicretizer<T>::process() {sort(m_data.begin(), m_data.end());int lastId = 0;for (int i = 1; i < m_data.size(); i++) {T x = m_data[i];if (x != m_data[lastId]) {m_data[++lastId] = x;}}while (lastId < m_data.size() - 1) {m_data.pop_back();}
}template<class T>
int Dicretizer<T>::get(T v) const {int l = -1, r = m_data.size();while (l + 1 < r) {int mid = (l + r) / 2;if (m_data[mid] >= v)r = mid;else l = mid;}if (r == m_data.size() || m_data[r] != v)return -1;return r;
}template<class T>
int Dicretizer<T>::size() const {return m_data.size();
}template<class T>
class FenwickTree {
private:vector<T>m_tree;int lowBit(int idx);
public:FenwickTree(int n):m_tree(n+1,0){}void update(int idx, T val);T query(int idx);T query(int l, int r);
};template<class T>
int FenwickTree<T>::lowBit(int idx) {return idx & -idx;
}template<class T>
void FenwickTree<T>::update(int idx, T val){int n = m_tree.size();while (idx < n) {m_tree[idx] += val;idx += lowBit(idx);}
}template<class T>
T FenwickTree<T>::query(int idx) {T sum = 0;while (idx > 0) {sum += m_tree[idx];idx -= lowBit(idx);}return sum;
}template<class T>
T FenwickTree<T>::query(int l, int r) {return query(r) - query(l - 1);
}#define maxn 1000001
int a[maxn];int main() {int n;cin >> n;Dicretizer<int>d;FenwickTree<int>ft(n);for (int i = 0; i < n; i++) {cin >> a[i];d.addData(a[i]);}d.process(); for (int i = 0; i < n; i++) {a[i] = d.get(a[i]) + 1; //树状数组的序号是从1开始,利用离散化给原数组每个值标记它的序号,也就是排序好它们的大小}long long sum = 0;for (int i = n - 1; i >= 0; i--) {ft.update(a[i], 1);sum += ft.query(a[i] - 1); //查询1~a[i]-1的元素和,他要找的是比它本身小的个数}cout << sum << endl;return 0;
}
三元组个数
这题就是遍历到的原数组的每一个元素用左边比它小的个数乘于右边比它大的个数,但是复杂度还是太高了,所以还得需要离散化数组,用树状数组记录比它小或大的个数
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;template<class T>
class Dicretizer {
private:vector<T>m_data;
public:void addData(T v);void process();int get(T v) const;int size() const;
};template<class T>
void Dicretizer<T>::addData(T v) {m_data.push_back(v);
}template<class T>
void Dicretizer<T>::process() {sort(m_data.begin(), m_data.end());int lastId = 0;for (int i = 1; i < m_data.size(); i++) {T x = m_data[i];if (x != m_data[lastId]) {m_data[++lastId] = x;}}while (lastId < m_data.size() - 1) {m_data.pop_back();}
}template<class T>
int Dicretizer<T>::get(T v) const {int l = -1, r = m_data.size();while (l + 1 < r) {int mid = (l + r) / 2;if (m_data[mid] >= v)r = mid;else l = mid;}if (r == m_data.size() || m_data[r] != v)return -1;return r;
}template<class T>
int Dicretizer<T>::size() const {return m_data.size();
}template<class T>
class FenwickTree {
private:vector<T>m_tree;int lowBit(int idx);
public:FenwickTree(int n):m_tree(n+1,0){}void update(int idx, T val);T query(int idx);T query(int l, int r);
};template<class T>
int FenwickTree<T>::lowBit(int idx) {return idx & -idx;
}template<class T>
void FenwickTree<T>::update(int idx, T val){int n = m_tree.size();while (idx < n) {m_tree[idx] += val;idx += lowBit(idx);}
}template<class T>
T FenwickTree<T>::query(int idx) {T sum = 0;while (idx > 0) {sum += m_tree[idx];idx -= lowBit(idx);}return sum;
}template<class T>
T FenwickTree<T>::query(int l, int r) {return query(r) - query(l - 1);
}#define mod 998244353
#define maxn 1000001
int a[maxn], lt[maxn]; //lt[i]表示小于a[i]的个数int main() {int n;cin >> n;Dicretizer<int>d;FenwickTree<int>ft(n);for (int i = 0; i < n; i++) {cin >> a[i];d.addData(a[i]);}d.process(); for (int i = 0; i < n; i++) {a[i] = d.get(a[i]) + 1; //树状数组的序号是从1开始,利用离散化给原数组每个值标记它的序号,也就是排序好它们的大小}for (int i = 0; i < n; i++) {ft.update(a[i], 1);lt[i] = ft.query(a[i] - 1);}ft = FenwickTree<int>(n);long long sum = 0;for (int i = n - 1; i >= 0; i--) {ft.update(a[i], 1);int gt = ft.query(a[i] + 1, n); //求出比a[i]大的个数sum += (long long)lt[i] * gt % mod;sum %= mod; //sum这个值可能超出范围所以还要取模}cout << sum << endl;return 0;
}
九、总结与学习建议
- 核心总结
树状数组是一种高效处理前缀和查询和单点更新的数据结构。
通过二进制索引和树形结构,将复杂度优化到 O(logn)。
- 学习建议
分类刷题:按问题类型集中练习(如动态前缀和、逆序对计数、区间更新)。
理解算法原理:掌握树状数组的实现步骤和优化技巧。
手写模拟过程:在纸上画出树状数组的结构,模拟操作过程,加深直观理解。
通过以上分析,树状数组作为一种高效的数据结构,在算法竞赛和实际应用中具有广泛用途。掌握其核心思想和实现技巧,能够有效提升算法效率。

希望大家可以一键三连,后续我们一起学习,谢谢大家!!!

相关文章:
进阶数据结构——树状数组
前言 看这篇文章前我建议你们先看这个视频还有这个视频,不然你们可能看不懂。 一、树状数组的核心思想与本质 核心思想:树状数组(Fenwick Tree)是一种用于高效处理前缀和查询和单点更新的数据结构。 本质:通过二进…...
键盘启用触摸板-tips
在日常使用笔记本电脑时,我们会遇到没带鼠标,触摸板关闭的情况,通常情况下,我们习惯通过鼠标点击或触摸屏操作来启用触摸板,但其实通过键盘也能轻松实现这一功能。以下就是一种通过键盘操作启用触摸板的方法࿰…...
信息安全之网络安全
网络安全技术是一类包含内容极其广泛的技术,广义上说任何检测、防御和抵制网络攻击的技术都属于网络安全技术,而且很多网络安全技术都是攻击驱动型的。 网络安全大致包含的内容主要有防火墙,入侵检测,漏洞扫描与网络隔离…...
成都国际数字影像产业园布局者树莓集团,亮相宜宾翠屏招商签约
在商业版图的不断拓展中,树莓集团始终以敏锐的市场洞察力和果敢的决策力占据先机。近期,作为成都国际数字影像产业园的布局者,树莓集团高调亮相宜宾翠屏招商签约盛会,引发行业内外的广泛关注。 宜宾翠屏招商签约盛会,…...
opencascade 获取edge起始点 会出现终点与实际不同的情况
在使用 OpenCASCADE 获取 TopoDS_Edge 的起始点和终点时,可能会出现终点与实际不一致的情况。这通常是由于以下原因导致的: 几何曲线的方向问题:在某些情况下,几何曲线的方向可能与拓扑边的方向不一致,导致通过几何曲线…...
掌握正则表达式_模式匹配的艺术
当然,以下是《掌握正则表达式:模式匹配的艺术》文章内容,使用 Java 正则表达式,并包含丰富的代码示例: 1. 引言 1.1 正则表达式的定义与历史 正则表达式(Regular Expression,简称 regex 或 regexp)是一种用于描述文本模式的强大工具。它最初由数学家 Stephen Kleene…...
【蓝桥】二维DP--摆花
📌题目描述 📌解题思路 📌完整代码 📌举例 📌题目描述 📌解题思路 动态规划(DP) 问题,核心是 “前 i 种物品,每种物品最多可以使用x 次,组成总和…...
在AMLOGIC android14 平台上使用adb
1.修改bootloader 编译:添加 --fastboot-write cd bootloader/uboot-repo ./mk s7d_bm201 --vab --avb2 --fastboot-write #./mk s7_bh201 --avb2 --vab --fastboot-write echo "compiled bootloader success!!!" cp build/u-boot.bin.signed ../../dev…...
力扣-二叉树-222 完全二叉树节点的数量
思路1 利用层序遍历所有节点即可 代码1 class Solution { public:int countNodes(TreeNode* root) {if(root nullptr) return 0;queue<TreeNode*> que;que.push(root);int size 0;while(!que.empty()){size que.size();int length que.size();while(length--){Tre…...
V93K测试机
爱德万V9300(又称V93K)是Advantest公司推出的高端可扩展SoC测试平台,在半导体测试领域具有标杆地位。以下为该设备的详细介绍: ### 一、核心性能与技术优势 1. **高速高精度测试能力** V9300支持高达112 Gbps PAM4信号&…...
【机器学习】监督学习-决策树-CART(Classification and Regression Tree,分类与回归树)详尽版
CART(Classification and Regression Trees)法 CART(分类与回归树)是一种决策树算法,由 Breiman 等人在 1984 年提出。它用于构建分类树(Classification Tree)或回归树(Regression …...
Navicat 迁移数据库 传输数据
Navicat提供的数据传输功能,很好用,可以从一个数据库迁移到另外一个数据库。 步骤:菜单栏----工具—传输—选择源连接和数据库----选择目的地连接和数据库...
Jetpack Compose初体验
入门学习 由于工作需要,我们当前要在老代码的基础上使用 Compose 进行新页面的开发,这项工作主要落在我的身上。因此,我需要先了解 Compose。 这里我入门看的是写给初学者的Jetpack Compose教程,Lazy Layout,有兴趣可…...
ceph部署-14版本(nautilus)-使用ceph-ansible部署实验记录
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、环境信息二、部署步骤2.1 基础环境准备2.2 各节点docker环境安装2.3 搭建互信集群2.4 下载ceph-ansible 三、配置部署文件3.1 使用本地docker3.2 配置hosts…...
【C++】C++ 旅馆管理系统(含 源码+报告)【独一无二】
👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉专__注👈:专注主流机器人、人工智能等相关领域的开发、测试技术。 系列文章目录 目录 系列文章目录一、设计要求二、设…...
快速排序
目录 什么是快速排序: 图解: 递归法: 方法一(Hoare法): 代码实现: 思路分析: 方法二(挖坑法): 代码实现: 思路分析: 非递…...
国内 ChatGPT Plus/Pro 订阅教程
1. 登录 chat.openai.com 依次点击 Login ,输入邮箱和密码 2. 点击升级 Upgrade 登录自己的 OpenAI 帐户后,点击左下角的 Upgrade to Plus,在弹窗中选择 Upgrade plan。 如果升级入口无法点击,那就访问这个网址,htt…...
易仓科技ai面试
请解释PHP中的面向对象编程的基本概念,并举例说明如何在PHP中定义一个类。 回答思路:需理解类、对象、继承和多态等基本概念,并能通过实例代码展示如何定义类及其属性和方法。 . 类(Class) 类是一个封装了数据和操作…...
LabVIEW用户界面(UI)和用户体验(UX)设计
作为一名 LabVIEW 开发者,满足功能需求、保障使用便捷与灵活只是基础要求。在如今这个用户体验至上的时代,为 LabVIEW 应用程序设计直观且具有美学感的界面,同样是不容忽视的关键任务。一个优秀的界面设计,不仅能提升用户对程序的…...
字玩FontPlayer开发笔记14 Vue3实现多边形工具
目录 字玩FontPlayer开发笔记14 Vue3实现多边形工具笔记整体流程临时变量多边形组件数据结构初始化多边形工具mousedown事件mousemove事件监听mouseup事件渲染控件将多边形转换为平滑的钢笔路径 字玩FontPlayer开发笔记14 Vue3实现多边形工具 字玩FontPlayer是笔者开源的一款字…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...
五、jmeter脚本参数化
目录 1、脚本参数化 1.1 用户定义的变量 1.1.1 添加及引用方式 1.1.2 测试得出用户定义变量的特点 1.2 用户参数 1.2.1 概念 1.2.2 位置不同效果不同 1.2.3、用户参数的勾选框 - 每次迭代更新一次 总结用户定义的变量、用户参数 1.3 csv数据文件参数化 1、脚本参数化 …...
使用 uv 工具快速部署并管理 vLLM 推理环境
uv:现代 Python 项目管理的高效助手 uv:Rust 驱动的 Python 包管理新时代 在部署大语言模型(LLM)推理服务时,vLLM 是一个备受关注的方案,具备高吞吐、低延迟和对 OpenAI API 的良好兼容性。为了提高部署效…...
【RabbitMQ】- Channel和Delivery Tag机制
在 RabbitMQ 的消费者代码中,Channel 和 tag 参数的存在是为了实现消息确认机制(Acknowledgment)和精细化的消息控制。 Channel 参数 作用 Channel 是 AMQP 协议的核心操作接口,通过它可以直接与 RabbitMQ 交互: 手…...
十、【ESP32开发全栈指南: TCP客户端】
一、TCP协议核心特性回顾 TCP与UDP关键差异 特性TCPUDP连接方式面向连接 (三次握手)无连接可靠性可靠传输 (重传/排序/校验)尽力交付数据顺序保证数据按序到达不保证顺序流控制滑动窗口机制无流控制传输效率协议开销大头部开销小适用场景文件传输、网页浏览实时音视频、广播通…...
