「数据结构详解·十五」树状数组

news2024/12/15 3:27:09
  • 「数据结构详解·一」树的初步
  • 「数据结构详解·二」二叉树的初步
  • 「数据结构详解·三」栈
  • 「数据结构详解·四」队列
  • 「数据结构详解·五」链表
  • 「数据结构详解·六」哈希表
  • 「数据结构详解·七」并查集的初步
  • 「数据结构详解·八」带权并查集 & 扩展域并查集
  • 「数据结构详解·九」图的初步
  • 「数据结构详解·十」双端队列 & 单调队列的初步
  • 「数据结构详解·十一」单调栈
  • 「数据结构详解·十二」有向无环图 & 拓扑排序
  • 「数据结构详解·十三」优先队列 & 二叉堆的初步
  • 「数据结构详解·十四」对顶堆
  • 「数据结构详解·十五」树状数组

0. 前置知识:lowbit 运算

我们定义 lowbit ( x ) \text{lowbit}(x) lowbit(x) x x x 在二进制下最低的 1 1 1 所代表的数。
lowbit ( 101 0 2 ) = 1 0 2 = 2 10 , lowbit ( 1110 1 2 ) = 1 2 = 1 10 \text{lowbit}(1010_2)=10_2=2_{10},\text{lowbit}(11101_2)=1_2=1_{10} lowbit(10102)=102=210,lowbit(111012)=12=110

我们要如何计算这个东西呢?
一种通常的计算方法是 lowbit ( x ) = x & ( ( ∼ x ) + 1 ) = x & − x \text{lowbit}(x)=x\&((\sim x)+1)=x\&-x lowbit(x)=x&((x)+1)=x&x,手模一下可以得到正确性。

现在你学会了计算 lowbit,下面就可以学习树状数组了!

1. 树状数组的概念

树状数组(Binary Indexed Tree, BIT, Fenwick Tree),也称作二叉索引树,是一种维护序列信息的数据结构。所维护的序列信息和运算需要满足一定的要求:

  • 可差分性:即如果知道了 a ∘ b a\circ b ab a a a,可以推得 b b b
  • 结合律:即维护的信息所做的运算 ∘ \circ 满足 ( a ∘ b ) ∘ c = a ∘ ( b ∘ c ) (a\circ b)\circ c=a\circ(b\circ c) (ab)c=a(bc)

在树状数组中,记 bit i \text{bit}_i biti区间 [ i- lowbit( i )+1, i ] \textbf{[\textit{i-}lowbit(\textit i)+1,\textit i]} [i-lowbit(i)+1,i] 的信息和
那么,对于一个序列 a a a 的前缀信息,都被划分成了 log \textbf{log} log
如图所示,底部的点是 a i a_i ai,上方的点是 bit i \text{bit}_i biti

下面以例题具体解释树状数组的实现。

2. 例题详解

2-1. Luogu P3374 【模板】树状数组 1 / Loj 130 树状数组 1 :单点修改,区间查询

思考一下我们修改位置 x x x 的数会影响的到的 bit i \text{bit}_i biti
结合上图的观察,可以发现,如果我们从下往上修改,当修改的是 x x x,则下一次修改的就是 x + lowbit ( x ) x+\text{lowbit}(x) x+lowbit(x)(具体证明留给读者思考)。

而查询 ∑ i = l r a i \sum\limits_{i=l}^ra_i i=lrai 可以拆成 ∑ i = 1 r a i − ∑ i = 1 l − 1 a i \sum\limits_{i=1}^ra_i-\sum\limits_{i=1}^{l-1}a_i i=1raii=1l1ai
显然查询前缀和我们只要不断减去当前的 lowbit \text{lowbit} lowbit 即可。

修改和查询的时间复杂度都是 O ( n log ⁡ n ) O(n\log n) O(nlogn)
具体实现:

#include<bits/stdc++.h>
using namespace std;

struct fwk{
    int n,bit[500005];

    void init(int i)
    {
        n=i;
        memset(bit,0,sizeof(bit));
    }

    void add(int i,int c)
    {
        for(;i<=n;i+=i&-i) bit[i]+=c;
    }

    int qry(int i)
    {
        int res=0;
        for(;i;i-=i&-i) res+=bit[i];
        return res;
    }
}bit;

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
	int n,m;
	cin>>n>>m;
    bit.init(n);
	for(int i=1;i<=n;i++)
	{
        int x;
		cin>>x;
		bit.add(i,x);
	}
	while(m--)
	{
		int op,x,y;
		cin>>op>>x>>y;
		if(op==1) bit.add(x,y);
		else cout<<bit.qry(y)-bit.qry(x-1)<<endl;
	}
    return 0;
}

2-2. Luogu P3368 【模板】树状数组 2 / Loj 131 树状数组 2 :区间修改,单点查询

我们 BIT 只能单点修改啊?怎么办!
其实,只要将序列 a a a 差分成为 b b b 后( b i = a i − a i − 1 b_i=a_i-a_{i-1} bi=aiai1),就变成了单点修改 b l ← b l + x , b r + 1 ← b r + 1 − x b_{l}\gets b_l+x,b_{r+1}\gets b_{r+1}-x blbl+x,br+1br+1x,区间查询 ∑ i = 1 x b i \sum\limits_{i=1}^xb_i i=1xbi 了。
代码和上一题几乎是一样的,所以不再展示了。

2-3. Luogu P3372 【模板】线段树 1 / Loj 132 树状数组 3 :区间修改,区间查询

这就不是很好做了。
首先区间查询就是 ∑ i = 1 r a i − ∑ i = 1 l − 1 a i \sum\limits_{i=1}^ra_i-\sum\limits_{i=1}^{l-1}a_i i=1raii=1l1ai,也就是说我们只要考虑如何求一个前缀和 ∑ i = 1 x a i \sum\limits_{i=1}^xa_i i=1xai
同上一题一样,将 a a a 差分得到 b b b,有这样的推导:
∑ i = 1 x a i = ∑ i = 1 x ∑ j = 1 i b j = b 1 + b 1 + b 2 + b 1 + b 2 + b 3 + ⋯ + b 1 + b 2 + b 3 + ⋯ + b x = ∑ i = 1 x ( x − i + 1 ) b i = ( x + 1 ) ∑ i = 1 x b i − ∑ i = 1 x i ⋅ b i \begin{aligned} &\sum\limits_{i=1}^xa_i\\ =&\sum\limits_{i=1}^x\sum\limits_{j=1}^ib_j\\ =&b_1+\\ &b_1+b_2+\\ &b_1+b_2+b_3+\\ &\cdots+\\ &b_1+b_2+b_3+\cdots+b_x\\ =&\sum\limits_{i=1}^x(x-i+1)b_i\\ =&(x+1)\sum\limits_{i=1}^xb_i-\sum\limits_{i=1}^xi\cdot b_i \end{aligned} ====i=1xaii=1xj=1ibjb1+b1+b2+b1+b2+b3++b1+b2+b3++bxi=1x(xi+1)bi(x+1)i=1xbii=1xibi
发现前后两个和式都是可以用 BIT 维护的!
于是就做完了。代码留给读者实现。

3. 值域树状数组

也称权值树状数组,就是将值的出现情况展现在 BIT 上,类似于哈希表。

以 Luogu P1908 逆序对 为例。
题目要求 i < j i<j i<j a i > a j a_i>a_j ai>aj 的二元组 ( i , j ) (i,j) (i,j) 个数。
考虑一个显然的事情:开一个桶,存放每种数的出现次数 c i c_i ci。那么当加入 a j a_j aj 时,能与 j j j 配对形成 ( i , j ) (i,j) (i,j) i i i 的个数就是 ∑ k = 1 i − 1 c k \sum\limits_{k=1}^{i-1}c_k k=1i1ck
而这个东西恰恰是可以使用 BIT 维护的!
代码也很简单,留给读者实现。

4. 二维树状数组

也称 BIT 套 BIT,也就是把 BIT 放到平面上。

以 Loj 133 二维树状数组 1:单点修改,区间查询 为例。
单点修改是同理的,只要在循环外再套一层就可以。

void add(int x,int y,int d)
{
	for(int i=x;i<=n;i+=i&-i)
	{
		for(int j=y;j<=m;j+=j&-j)
		{
			bit[i][j]+=d;
		}
	}
}

区间查询同理。但是注意查询的是 ∑ i = 1 x ∑ j = 1 y a i , j \sum\limits_{i=1}^x\sum\limits_{j=1}^ya_{i,j} i=1xj=1yai,j 的信息,即二维前缀和,减一减即可。

int qry(int x,int y)
{
	int res=0;
	for(int i=x;i;i-=i&-i)
	{
		for(int j=y;j;j-=j&-j)
		{
			res+=bit[i][j];
		}
	}
	return res;
}
int qry(int a,int b,int c,int d){return qry(c,d)-qry(c,b-1)-qry(a-1,d)+qry(c-1,d-1);}

而 Loj 134 二维树状数组 2:区间修改,单点查询 做二维差分即可。

但是,Luogu P4514 上帝造题的七分钟 / Loj 135 二维树状数组 3:区间修改,区间查询 就不是这样的了。

和一维的同理,令 b i , j = a i , j − a i − 1 , j − a i , j − 1 + a i − 1 , j − 1 b_{i,j}=a_{i,j}-a_{i-1,j}-a_{i,j-1}+a_{i-1,j-1} bi,j=ai,jai1,jai,j1+ai1,j1 后推一个式子:
∑ i = 1 x ∑ j = 1 y a i , j = ∑ i = 1 x ∑ j = 1 y ∑ p = 1 i ∑ q = 1 j b p , q = ∑ i = 1 x ∑ j = 1 y ( x − i + 1 ) ( y − j + 1 ) b i , j = ∑ i = 1 x ∑ j = 1 y ( ( x + 1 ) ( y + 1 ) − ( x + 1 ) j − ( y + 1 ) i + i ⋅ j ) b i , j = ( x + 1 ) ( y + 1 ) ∑ i = 1 x ∑ j = 1 y b i , j − ( x + 1 ) ∑ i = 1 x ∑ j = 1 y j ⋅ b i , j − ( y + 1 ) ∑ i = 1 x ∑ j = 1 y i ⋅ b i , j + ∑ i = 1 x ∑ j = 1 y i ⋅ j ⋅ b i , j \def\s{\sum\limits} \begin{aligned} &\s_{i=1}^x\s_{j=1}^ya_{i,j}\\ =&\s_{i=1}^x\s_{j=1}^y\s_{p=1}^i\s_{q=1}^jb_{p,q}\\ =&\s_{i=1}^x\s_{j=1}^y(x-i+1)(y-j+1)b_{i,j}\\ =&\s_{i=1}^x\s_{j=1}^y((x+1)(y+1)-(x+1)j-(y+1)i+i\cdot j)b_{i,j}\\ =&(x+1)(y+1)\s_{i=1}^x\s_{j=1}^yb_{i,j}-(x+1)\s_{i=1}^x\s_{j=1}^yj\cdot b_{i,j}-(y+1)\s_{i=1}^x\s_{j=1}^yi\cdot b_{i,j}+\s_{i=1}^x\s_{j=1}^yi\cdot j\cdot b_{i,j} \end{aligned} ====i=1xj=1yai,ji=1xj=1yp=1iq=1jbp,qi=1xj=1y(xi+1)(yj+1)bi,ji=1xj=1y((x+1)(y+1)(x+1)j(y+1)i+ij)bi,j(x+1)(y+1)i=1xj=1ybi,j(x+1)i=1xj=1yjbi,j(y+1)i=1xj=1yibi,j+i=1xj=1yijbi,j
于是开 4 4 4 个 BIT 分别维护 b i , j , i ⋅ b i , j , j ⋅ b i , j , i ⋅ j ⋅ b i , j b_{i,j},i\cdot b_{i,j},j\cdot b_{i,j},i\cdot j\cdot b_{i,j} bi,j,ibi,j,jbi,j,ijbi,j 即可。

5.巩固练习

事实上,BIT 还有很多应用,如维护不可差分的信息等。但这些通常是用其他数据结构平替的,故不在此继续做阐述。感兴趣的读者可以自行了解。

  • 上述未给出代码的例题
  • Luogu P2068 统计和
  • Luogu P2357 守墓人
  • Luogu P1774 最接近神的人
  • Luogu P1637 三元上升子序列

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

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

相关文章

【sgFileLink】自定义组件:基于el-link、el-icon标签构建文件超链接组件,支持垃圾桶删除、点击预览视频/音频/图片/PDF格式文件

sgFileLink源代码 <template><div :class"$options.name"><el-link click.stop"clickFile(data)"><img :src"getSrc(data)" /><span>{{ getFileNameAndSize(data) }}</span></el-link><el-linkcl…

电机驱动模块L9110S详解

电机驱动模块是一种用于控制和驱动电机的设备&#xff0c;它能够将控制信号转化为适合电机操作的电流和电压。通过电机驱动模块&#xff0c;可以实现对电机的速度、方向等参数进行精确控制。 今天我们要介绍的 L9110S 电机驱动适合大学生、工程师、个人DIY、电子爱好者们学习和…

Unity 获取鼠标点击位置物体贴图颜色

实现 Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit)) {textureCoord hit.textureCoord;textureCoord.x * textureMat.width;textureCoord.y * textureMat.height;textureColor textureMat.GetPixel(Mathf.Flo…

openlayers+vite+vue3实现在地图上画线(四)

在前几期实现离线地图初始化以及规划某一特定区域、打点、出现弹窗的基础上&#xff0c;本文主要阐述如何实现在所规划的区域地图上画线&#xff0c;如果你实现了打点的效果&#xff0c;其实这个相对来说还是算比较简单的&#xff0c;因为和打点的代码大差不差。使用openlayers…

游戏引擎学习第45天

仓库: https://gitee.com/mrxiao_com/2d_game 回顾 我们刚刚开始研究运动方程&#xff0c;展示了如何处理当人物遇到障碍物时的情况。有一种版本是角色会从障碍物上反弹&#xff0c;而另一版本是角色会完全停下来。这种方式感觉不太自然&#xff0c;因为在游戏中&#xff0c;…

类与对象以及ES6的继承

认识class定义类 类的声明用的比较多 类与构造函数的异同 类的构造函数 类的实例方法 类的访问器方法 在类里面写拦截方法 类的静态方法 通过类名直接访问 es6类的继承-extends super关键字 子类可以重写父类方法包括父类的静态方法也可以继承父类的静态方法 babel可以将新的代…

通过IKE协商方式建立IPSec隧道

我们前面学习了H3C的IPsec VPN配置&#xff08;为什么IPsec两端内网的网段能不能重复&#xff1f;分明可以实现&#xff01;&#xff09;&#xff0c;学习了Juniper的IPsec VPN配置&#xff0c;学习了Windows的IPsec VPN配置&#xff08;配置Juniper虚墙vSRX基于策略的IPsec VP…

文献分享: EMVB——PLAID后期交互引擎的进一步优化

&#x1f449;前情提要&#xff1a; 神经网络自然语言模型概述 Transformer \text{Transformer} Transformer与注意力机制概述 &#x1f4da;相关论文&#xff1a; BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding \text{BERT: Pre-train…

vue2+element-ui实现多行行内表格编辑

效果图展示 当在表格中点击编辑按钮时:点击的行变成文本框且数据回显可以点击确定按钮修改数据或者取消修改回退数据: 具体实现步骤 1. 行数据定义编辑标记 行数据定义编辑标记 当在组件中获取到用于表格展示数据的方法中,针对每一行数据添加一个编辑标记 this.list.f…

介绍几个Linux下的杀毒软件

一&#xff1a;chkrootkit 是一个用于检测Linux系统下可能被攻击者植入的后门程序或恶意代码的扫描工具。 &#xff08;1&#xff09;安装方法&#xff08;ubuntu) sudo apt update sudo apt install chkrootkit &#xff08;2&#xff09;使用方法&#xff1a; chkrootkit -…

JS 中请求队列与锁的巧妙结合

一、引言 在 JavaScript 开发中&#xff0c;尤其是在涉及到异步操作和对共享资源的并发访问时&#xff0c;有效地控制请求顺序和资源访问权限至关重要。例如&#xff0c;在多个网络请求同时针对一个有限制访问频率的 API 或者多个异步任务竞争同一个文件写入权限的场景下&#…

MYSQL索引的分类和创建

目录 1、聚簇索引和非聚簇索引 tips&#xff1a; 小问题&#xff1a;主键为什么建议使用自增id? 2、普通索引 &#xff08;常规索引&#xff09;(normal) 3、唯一索引&#xff08;UNIQUE &#xff09; 唯一索引和主键的区别&#xff1a; 唯一约束和唯一索引的区别&#…

Oracle最佳实践-优化硬解析

前段时间参加oracle CAB&#xff0c;oracle高级服务部门做了一个数据库最佳实践的报告&#xff0c;其中就有一项就是解决未使用绑定变量但执行次数很多的SQL&#xff1b; 对于一个数据库来说如果不知道该如何优化&#xff0c;那么最简单最有效的优化就是减少硬解析&#xff0c;…

AI Agent:重塑业务流程自动化的未来力量(2/30)

《AI Agent&#xff1a;重塑业务流程自动化的未来力量》 摘要&#xff1a;整体思路是先介绍 AI Agent 的基本情况&#xff0c;再深入阐述其实现业务流程自动化的方法和在不同领域的应用&#xff0c;接着分析其价值和面临的挑战&#xff0c;最后得出结论&#xff0c;为读者全面…

哈默纳科Harmonic谐波减速机机器人精准高效动力传递的核心力量

在当今科技飞速发展的时代&#xff0c;机器人技术正以惊人的速度改变着我们的生产与生活方式。而在机器人的精密机械结构中&#xff0c;哈默纳科 Harmonic 谐波减速机扮演着不可或缺的角色&#xff0c;成为机器人精准高效动力传递的关键所在。 1.高精度与灵活性&#xff1a;哈默…

【开源项目】经典开源项目数字孪生体育馆—开源工程及源码

飞渡科技数字孪生体育馆管理平台&#xff0c;融合物联网IOT、BIM数据模型、三维GIS等技术&#xff0c;实现体育馆的全方位监控和实时全局掌握&#xff0c;同时&#xff0c;通过集成设备设施管理、人员管理等子系统&#xff0c;减少信息孤岛&#xff0c;让场馆“可视、可控、可管…

长短期记忆神经网络(LSTM)介绍

1、应用现状 长短期记忆神经网络&#xff08;LSTM&#xff09;是一种特殊的循环神经网络(RNN)。原始的RNN在训练中&#xff0c;随着训练时间的加长以及网络层数的增多&#xff0c;很容易出现梯度爆炸或者梯度消失的问题&#xff0c;导致无法处理较长序列数据&#xff0c;从而无…

SQL server学习03-创建和管理数据表

目录 一&#xff0c;SQL server的数据类型 1&#xff0c;基本数据类型 2&#xff0c;自定义数据类型 二&#xff0c;使用T-SQL创建表 1&#xff0c;数据完整性的分类 2&#xff0c;约束的类型 3&#xff0c;创建表时创建约束 4&#xff0c;任务 5&#xff0c;由任务编写…

正则表达式——参考视频B站《奇乐编程学院》

智能指针 一、背景&#x1f388;1.1. 模式匹配&#x1f388;1.2. 文本替换&#x1f388;1.3. 数据验证&#x1f388;1.4. 信息提取&#x1f388;1.5. 拆分字符串&#x1f388;1.6. 高级搜索功能 二、原料2.1 参考视频2.2 验证网址 三、用法3.1 限定符3.1.1 ?3.1.2 *3.1.3 3.1.…

Elasticsearch 集群部署

Elasticsearch 是一个分布式的搜索和分析引擎&#xff0c;广泛应用于日志分析、全文搜索、实时数据分析等场景。它以其高性能、高可用性和易用性而著称。本文档将引导您完成一个基本的 Elasticsearch 集群配置&#xff0c;包括节点间的通信、客户端访问、安全设置等关键步骤。我…