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

一、树状数组的核心思想与本质
核心思想:树状数组(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是笔者开源的一款字…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
