数据结构—树状数组

news2024/11/22 16:32:55

树状数组

  • 单点修改、区间查询
  • 区间修改、单点查询
  • 区间修改、区间查询

单点修改、区间查询

这里讲解树状数组的最基本操作单点修改、区间查询,当然能做到单点修改、区间查询,肯定就能做到单点修改、单点查询了。树状数组是用来快速求前缀和的,传统的单点修改、区间查询要么单点修改的复杂度是 O ( n ) O(n) O(n)查询是 O ( 1 ) O(1) O(1),要么查询复杂度是 O ( n ) O(n) O(n)修改是 O ( 1 ) O(1) O(1),树状数组相当于一个比较综合的算法,树状数组的查询时间复杂度和修改时间复杂度都是 O ( l o g n ) O(log n) O(logn)的。

下面这张图基本上是讲解树状数组必用的一张图
在这里插入图片描述
树状数组初学起来还是比较难以理解,过弄明白之后代码就很好写了,树状数组相当于是拿空间换时间,原数组为a[],额外数组为c[],先来讲一下lowbit操作

lowbit(x)返回x的最后一个1及其后面的0(可能不存在)构成的数字
比如
lowbit(3)=1,	3的二进制是11	,最后一位1及其后面的01
lowbit(4)=4,	4的二进制是100	,最后一位1及其后面的0100
lowbit(6)=2, 4的二进制是110	,最后一位1及其后面的010
lowbit函数也很好实现
lowbit(x)=x&-x
因为计算机是存的补码,-x相当于x取反再加1,例如x=10010,-x=1110
-x&x=10

树状数组主要有两个操作:

1.某一位置的数+x 
2.求区间[1,r]的和([l,r]=[1,r]-[1,l-1]

观察上图可以发现

c[1]=a[1]
c[2]=c[1]+a[2]
c[3]=a[3]
c[4]=c[2]+c[3]+a[4]
...
c[16]=c[8]+c[12]+c[14]+c[15]+a[16]

假设我们已经维护好了c[]数组,那么我们如何求前缀和呢
比如求a[1~15]的和
我们可以发现

15的二进制为1111=1000+100+10+1=8+4+2+1
sum[1,15]=c[15]+c[14]+c[12]+c[8]=c[15-0]+c[15-0-1]+c[15-0-1-2]+c[15-0-1-2]+c[15-0-1-2-4]
有没有发现有什么关系
其中
lowbit(1111)=1,1111-1=1110
lowbit(1110)=2,1110-2=1100
lowbit(1100)=4,1100-4=1000
lowbit(1000)=8,1000-8=0

所以根据上面的推导就能写出查询函数query(r),查询[1,x]的前缀和

int query(int r){
    int res=0;
    for(int i=r;i>=0;i-=lowbit(i)) res+=f[i];
    return res;
}

这是查询操作,我们还需要修改操作,每次修改a[](将a[i]变成b,就相当于a[i]+b-a[i],更改操作可以转换成+操作)都需要更改其对应的c[]数组的值

a[2]对应的c[]数组有
c[2]、c[4]、c[8]、c[16],对应的二进制下标为
 10   100  1000  10000
其中10=2
4=100=2+lowbit(2)
8=1000=4+lowbit(4)
16=10000=8+lowbit(8)

所以某一位置的数+x 的函数为

void add(int x,int c){
    for(int i=x;i<=n;i+=lowbit(i))  f[i]+=c;//这里f数组是上图中的c数组,c是要加的值x
}

至于为什么操作可以看b站这两个视频 五分钟丝滑动画讲解 | 树状数组、 〔manim | 算法 | 数据结构〕 完全理解并深入应用树状数组
有一道树状数组的最经典例题可以做一下,AcWing1264. 动态求连续区间和
代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5+10;

int f[N];
int n,m;
int lowbit(int x){
    return x&-x;
}
void add(int x,int c){
    for(int i=x;i<=n;i+=lowbit(i))  f[i]+=c;
}
int query(int r){
    int res=0;
    for(int i=r;i>=1;i-=lowbit(i)) res+=f[i];
    return res;
}

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        add(i,x);
    }
    while(m--){
        int k,l,r;
        scanf("%d%d%d",&k,&l,&r);
        if(k==0) printf("%d\n",query(r)-query(l-1));
        else add(l,r);
    }
    return 0;
}

还有两道例题:
AcWing1265. 数星星
AcWing 241. 楼兰图腾

区间修改、单点查询

之前的单点修改区间查询,维护的是原数组的树状数组,这里我们维护差分数组的树状数组即可,可看例题 AcWing 242. 一个简单的整数问题
代码如下

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5+10;

int n,m;
int c[N];

int lowbit(int x){
    return x&-x;
}
void add(int idx,int x){
    for(int i=idx;i<=n;i+=lowbit(i)) c[i]+=x;
}
int query(int r){
    int res=0;
    for(int i=r;i;i-=lowbit(i)) res+=c[i];
    return res;
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        add(i,x);
        add(i+1,-x);
    }
    while(m--){
        char op[2];
        cin>>op;
        if(*op=='Q'){
            int r;
            cin>>r;
            cout<<query(r)<<endl;
        }
        else{
            int l,r,d;
            cin>>l>>r>>d;
            add(l,d);
            add(r+1,-d);
        }
    }
    return 0;
}

区间修改、区间查询

区间修改,区间查询较为复杂一些,要维护两个树状数组
首先有差分数组b[]原数组f[]

首先初始化有

b[1]=f[1]
b[2]=f[2]-f[1]
b[3]=f[3]-f[2]
......
b[n]=f[n]-f[n-1]

我们要区间修改,区间查询,区间修改可以通过差分来实现,区间查询的推导如下
要求区间[1,x]的和,就是求 ∑ i = 1 x f [ i ] \sum\limits_{i=1}^{x}f[i] i=1xf[i] ,其中 f [ i ] = ∑ j = 1 i b [ j ] f[i]=\sum\limits_{j=1}^{i}b[j] f[i]=j=1ib[j] ∑ i = 1 x f [ i ] = ∑ i = 1 x ∑ j = 1 i b [ j ] \sum\limits_{i=1}^{x}f[i]=\sum\limits_{i=1}^{x}\sum\limits_{j=1}^{i}b[j] i=1xf[i]=i=1xj=1ib[j]为了方便看,我们可以写成下面这种形式

f[1]= b[1]
f[2]= b[1]+b[2]
f[3]= b[1]+b[2]+b[3]
.....
f[x]= b[1]+b[2]+b[3]+.....+b[x]

右侧的这部分像矩阵,我们把这个矩阵补全

\b[1]+b[2]+b[3]+b[4]+.....+b[x]
b[1]\+b[2]+b[3]+b[4]+.....+b[x]
b[1]+b[2]\+b[3]+b[4]+.....+b[x]
b[1]+b[2]+b[3]\b[4]+.....+b[x]
.....				    \+b[x]	
b[1]+b[2]+b[3]+.....     +b[x]

这个矩阵的值为 ( x + 1 ) ∗ ( b [ 1 ] + b [ 2 ] + . . . . + b [ x ] ) (x+1)*(b[1]+b[2]+....+b[x]) (x+1)(b[1]+b[2]+....+b[x]),我们补全这个矩阵用到的数的和为 1 ∗ b [ 1 ] + 2 ∗ b [ 2 ] + . . . + x ∗ b [ x ] 1*b[1]+2*b[2]+...+x*b[x] 1b[1]+2b[2]+...+xb[x]
所以 ∑ i = 1 x f [ i ] = ∑ i = 1 x ∑ j = 1 i b [ j ] \sum\limits_{i=1}^{x}f[i]=\sum\limits_{i=1}^{x}\sum\limits_{j=1}^{i}b[j] i=1xf[i]=i=1xj=1ib[j]= ( x + 1 ) ∗ ( b [ 1 ] + b [ 2 ] + . . . . + b [ x ] ) − ( 1 ∗ b [ 1 ] + 2 ∗ b [ 2 ] + . . . + x ∗ b [ x ] ) (x+1)*(b[1]+b[2]+....+b[x])-(1*b[1]+2*b[2]+...+x*b[x]) (x+1)(b[1]+b[2]+....+b[x])(1b[1]+2b[2]+...+xb[x])
我们需要两个树状数组c1[]、c2[],c1是b数组的树状数组,c2是i*b[i]的树状数组
树状数组主要有两个操作,查询操作,这两个数组的操作是一样的,对于修改操作,例如在[l,r]区间内加上一个数h
对于c1数组的修改操作为

add(c1,l,h)
add(c1,r+1,-h)

对于c2数组的修改操作为

add(c2,l,l*h)
add(c2,r+1,-(r+1)*h)

例题: AcWing 243. 一个简单的整数问题2
代码如下

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
typedef long long ll;
using namespace std;
const int N=1e5+10;

ll c1[N],c2[N];
int f[N];
int n,m;

int lowbit(int x){
    return x&-x;
}
void add(ll c[],int idx,ll x){
    for(int i=idx;i<=n;i+=lowbit(i)) c[i]+=x;
}
ll query(ll c[],int r){
    ll res=0;
    for(int i=r;i;i-=lowbit(i)) res+=c[i];
    return res;
}

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) scanf("%d",&f[i]);
    for(int i=1;i<=n;i++){
        add(c1,i,f[i]-f[i-1]);
        add(c2,i,(ll)(f[i]-f[i-1])*i);
    }
    while(m--){
        char op[2];
        int l,r,d;
        cin>>op;
        if(*op=='C'){
            cin>>l>>r>>d;
            add(c1,l,d);
            add(c1,r+1,-d);
            add(c2,l,(ll)l*d);
            add(c2,r+1,-(r+1)*(ll)d);
        }
        else{
            cin>>l>>r;
            cout<<(ll)(r+1)*query(c1,r)-(ll)query(c2,r)-(ll)(l)*query(c1,l-1)+(ll)query(c2,l-1)<<endl;
        }
    }
}

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

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

相关文章

MGRE之OSPF实验

目录 题目&#xff1a; 步骤二&#xff1a;拓扑设计与地址规划​编辑 步骤三&#xff1a;IP地址配置 步骤四&#xff1a;缺省路由配置 步骤五&#xff1a;NAT的配置 步骤六&#xff1a;MGRE配置 中心站点R1配置 分支站点配置 中心站点R5 R1配置 分支站点配置 检测&…

UE 材质学习补充

Add Name Reroute Node ...&#xff08;本地变量&#xff09; 该节点可以整理节点&#xff0c;优化界面 Texture Texture(纹理图像)&#xff0c;一般由RGB三个通道混合构成&#xff0c;RGB三个通道的值代表亮度&#xff0c;RGB三个通道分别都是0-1&#xff08;0-255&#xff09…

征服FarmerJohn(二) Naptime【USACO05JAN】

题解目录 前言题目内容题目描述输入输出样例题目思路示例代码AC图片 后记往期精彩 前言 在上一期征服FarmerJohn&#xff08;一&#xff09;三角形【USACO2020FEB-B】结束之后&#xff0c;我们来看一道难度有所提升的DP问题&#xff0c;也就是常说的动态规划&#xff0c;今天我…

Please set the ROCKETMQ_HOME variable in your environment!

原因 启动ROCKETMQ执行命令start mqnamesrv.cmd时报错 翻译意思是请在您的环境中设置ROCKETMQ_HOME变量&#xff01; 查看mqnamesrv.cmd可以看到如果"%ROCKETMQ_HOME%\bin\runserver.cmd"不存在会报此错误 配置上环境变量ROCKETMQ_HOME即可

《深入理解计算机系统》(美)布赖恩特(Bryant,R.E.) 等

适合对象&#xff1a;对计算机感兴趣的朋友。 需要相关资料的可私信我。 持续更新中&#xff1a; 第一章&#xff1a;计算机系统漫游 主要知识点&#xff1a;解读全书结构框架&#xff0c;解释OS的原理和相关硬件软件。计算机系统是由硬件和系统软件组成&#xff0c;共同协作…

kafka消费者api和分区分配和offset消费

kafka消费者 消费者的消费方式为主动从broker拉取消息&#xff0c;由于消费者的消费速度不同&#xff0c;由broker决定消息发送速度难以适应所有消费者的能力 拉取数据的问题在于&#xff0c;消费者可能会获得空数据 消费者组工作流程 Consumer Group&#xff08;CG&#x…

如何在 SwiftUI 中使用 Touch ID 和 Face ID?

1. 需要通过指纹&#xff0c;面容认证后才能打开 App 2. 添加配置 需要向 Info.plist 文件中添加一个配置&#xff0c;向用户说明为什么要访问 添加 Privacy - Face ID Usage Description 并为其赋予值 $(PRODUCT_NAME) need Touch Id or Face ID permission for app lock 3. …

RTC在不同业务场景下的最佳音质实践

背景介绍 WebRTC是目前实时音视频领域最流行的开源框架。2010年Google收购GIPS引擎后&#xff0c;将其纳入Chrome体系且开源后&#xff0c; 命名为“WebRTC”。WebRTC获得各大浏览器厂商的支持并纳入W3C标准&#xff0c;促进了实时音视频在移动互联网应用中的 普及。2021年1月&…

算法练习——力扣随笔【LeetCode】【C++】

文章目录 LeetCode 练习随笔力扣上的题目和 OJ题目相比不同之处&#xff1f;定义问题排序问题统计问题其他 LeetCode 练习随笔 做题环境 C 中等题很值&#xff0c;收获挺多的 不会的题看题解&#xff0c;一道题卡1 h &#xff0c;多来几道&#xff0c;时间上耗不起。 力扣上的题…

Pytorch个人学习记录总结 06

目录 神经网络-卷积层 torch.nn.Conv2d 神经网络-最大池化的使用 torch.nn.MaxPool2d 神经网络-卷积层 torch.nn.Conv2d torch.nn.Conv2d的官方文档地址 CLASS torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue,…

TMS FNC Dashboard Pack Crack

TMS FNC Dashboard Pack Crack TTMSFNCWidgetProgress&#xff1a;循环进度指示器 TTMSFNCWidget设定值&#xff1a;带范围和设定值的值指示器 TTMSFNCWidgetMultiProgress&#xff1a;多个值的基于同心圆的进度指示器 TTMSFNCWidgetDistributionIndicator&#xff1a;各种模式…

【Kubernetes部署篇】ingress-nginx高可用架构实施部署

文章目录 一、环境说明二、实施过程1、部署Ingress Controller2、安装并配置Nginx3、安装并配置Keepalived3、测试keepalived主备切换 三、创建Ingress规则&#xff0c;测试七层转发 一、环境说明 1、环境说明&#xff1a; IP地址主机名称备注16.32.15.201node-1K8S节点16.32…

AMS358i和施耐德TM241 EtherNet 通信

产品、配件及工具型号 设备名称 型号 数量 激光测距 AMS358i 1 直流电源24VDC 1 连接电缆 KD U-M12-5A-V1-050 1 交换机 1 施耐德PLC TM241 1 AMS358i通信网线 KSS ET-M12-4A-RJ45-A-P7-020 1 网线 双向水晶头 2 电气连接图及说明 点击桌面的Somachi…

【NLP】使用 Keras 保存和加载深度学习模型

一、说明 训练深度学习模型是一个耗时的过程。您可以在训练期间和训练后保存模型进度。因此&#xff0c;您可以从上次中断的地方继续训练模型&#xff0c;并克服漫长的训练挑战。 在这篇博文中&#xff0c;我们将介绍如何保存模型并使用 Keras 逐步加载它。我们还将探索模型检查…

虹科活动 | 虹科ADAS自动驾驶研讨会

​​虹科ADAS/自动驾驶研讨会将于8月7日在上海闵行展开——加快ADAS/AD开发步伐&#xff01; 期待您的参与&#xff01;

Day45: 300.最长递增子序列,674. 最长连续递增序列,718. 最长重复子数组

目录 300.最长递增子序列 思路 674. 最长连续递增序列 思路 718. 最长重复子数组 思路 300.最长递增子序列 300. 最长递增子序列 - 力扣&#xff08;LeetCode&#xff09; 思路 1. 确定dp数组及其下标含义 dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列…

【每日运维】判断服务器时间同步是否正常

对于 ntpd 服务 ntpq -premote&#xff1a;时间同步源的 IP 地址或域名refid&#xff1a;参考 ID&#xff0c;它是一个代表时间源的唯一标识符st&#xff1a;层级&#xff0c;表示时间同步源的层级关系。较低的层级意味着更接近原子钟的时间源t&#xff1a;状态&#xff0c;表…

uni-app:script中设置的data,在界面的显示(包含图片src为data中的数据该如何展示),以及控制台的输出

样式&#xff1a; 两个图标的区别&#xff1a; 第一个图标是图片文件直接在文件夹static中展示 前台代码展示&#xff1a; <image class"logo" src"/static/logo.png"></image> 第二个图标是从服务器端进行的引用 在script中的data中进行的设…

【C++修炼之路】stl 中的容器适配器

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;StackFrame &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录 一、stack二、queue三、deque四、priority_queue1、仿函数2、实现 如果无聊的话&#xff0c;就来逛逛 我的博客栈 吧! &#x1…

从新手到大师:优雅的Vim熟练之旅(万文详解)

从新手到大师&#xff1a;优雅的Vim熟练之旅 博主简介一、前言1.1、Vim编辑器的重要性和流行性1.2、目标 二、Vim简介2.1、什么是Vim2.2、历史和背景简介2.3、Vim的优势和适用场景 三、安装和设置Vim3.1、下载和安装Vim编辑器3.2、基本配置&#xff1a;.vimrc文件的重要性和常用…