热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

树状数组(1.单点修改,区间查询2.区间修改,单点查询)

部分转载及其图片引用自?树状数组 数据结构详解与模板。目录前言树状数组 1:单点修改,区间查询题目描述lowbit函数单点更新区间查询前缀和C++代码树状数组 2:区间修改,单点查询题目描述区间更新差

部分转载及其图片引用自?树状数组 数据结构详解与模板。


目录

  • 前言
  • 树状数组 1:单点修改,区间查询
    • 题目描述
    • lowbit函数
    • 单点更新
    • 区间查询
      • 前缀和
    • C++代码
  • 树状数组 2:区间修改,单点查询
    • 题目描述
    • 区间更新
      • 差分
    • 单点查询
    • C++代码
前言

对于这样一个问题:给定数组a,有两种操作,第一个操作是使第i个数加上c,即a[i] + c;第二个
操作是查询区间 [L, R] 中各个数的累加和即a[L] + a[L + 1] + ... + a[R]
那么,朴素做法的话就是对于操作一使得a[i] += c,对于操作二遍历一遍从LR,然后各元素依次加上,这样做的时间复杂度分别为 O(1)O(n) 。也很容易想到利用前缀和来优化一下,但最终也会发现,虽然操作二的复杂度优化为 O(1) 了,但是在操作一上修改前缀和数组需要的复杂度却变为了 O(n)
所以这时候提出了树状数组的存储数据的结构,使得对于上面两个操作,复杂度均为 O(logN)

下面便是树状数组的二叉树形式:

假设对于a[]数组,它对应到的树为另一数组tree[],那么第i个数a[i]的意义就是结点tree[i]

标记为灰色的节点实际已被上层覆盖,不占据空间。

在这里插入图片描述
注意:任意结点tree[i]中存储的数据是所有子节点的和这句话很关键,它是优化更新和查询操作中循环次数的重要依据。

下面是二进制版本,能看到(绿色箭头的指向

更新过程是每次加了个二进制的低位1(101+1 ->110, 110 + 10 -> 1000, 1000 + 1000 -> 10000)

查询过程每次就是去掉了二进制中的低位1(1111 - 1 -> 1110, 1110 - 10 -> 1100, 1100 - 100 -> 1000)
在这里插入图片描述


树状数组 1:单点修改,区间查询

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 x xx

  • 求出某区间每一个数的和

输入格式

第一行包含两个正整数 n , m n,mn,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n nn 个用空格分隔的整数,其中第 i ii 个数字表示数列第 i ii 项的初始值。

接下来 m mm 行每行包含 3 33 个整数,表示一个操作,具体如下:

  • 1 x k 含义:将第 x xx 个数加上 k kk

  • 2 x y 含义:输出区间 [ x , y ] [x,y][x,y] 内每个数的和

输出格式

输出包含若干行整数,即为所有操作 2 22 的结果。

样例

样例输入

5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4

样例输出

14
16

提示

【数据范围】

对于 30 % 30\%30% 的数据,1 ≤ n ≤ 8 1 \le n \le 81n81 ≤ m ≤ 10 1\le m \le 101m10
对于 70 % 70\%70% 的数据,1 ≤ n , m ≤ 1 0 4 1\le n,m \le 10^41n,m104
对于 100 % 100\%100% 的数据,1 ≤ n , m ≤ 5 × 1 0 5 1\le n,m \le 5\times 10^51n,m5×105

样例说明:

故输出结果14、16。


lowbit函数

由于更新和查询过程中,任一结点x的下一个位置都与其二进制表示中最低位的1有关系,所以定义lowbit(x)是取出x的最低位1。

int lowbit(int x){ return x & (-x); }

单点更新

继续看开始给出的图

此时如果我们要更改A[1]

则有以下需要进行同步更新

1(001) tree[1]+=A[1]

lowbit(1)=001 1+lowbit(1)=2(010) tree[2]+=A[1]

lowbit(2)=010 2+lowbit(2)=4(100) tree[4]+=A[1]

lowbit(4)=100 4+lowbit(4)=8(1000) tree[8]+=A[1]

所以得出结论是:更新点x后,还需要不停往上更新包含了x的父结点直到更新至根节点n为止,而父结点的下标由x + lowbit(x)而来

void update(int x, int k){
while(x <= n){
tree[x] += k;
x += lowbit(x); //x的父节点
}
}

区间查询

前缀和

借助前缀和的思想,求 [L,R] 区间的Σ可以利用R的前缀和减去L - 1的前缀和,而这个前缀和的求法如下:

举个例子 x=5

tree[4]=A[1]+A[2]+A[3]+A[4];

tree[5]=A[5];

可以推出: sum(i = 5) ==> tree[4]+tree[5];

序号写为二进制: sum(101)=tree[(100)]+tree[(101)];

第一次101,减去最低位的1就是100。

其实也就是单点更新的逆操作

int getsum(int x){ //先求前缀和
int sum = 0;
while(x){
sum += tree[x];
x -= lowbit(x);
}
return sum;
}
int query(int x, int y){ //性质
return getsum(y) - getsum(x - 1);
}


C++代码

#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 2000010;
int tree[N]; //实际上不会用到原数组a,两个操作的对象均是树,所以可以直接考虑存一个树
int op, n, m, x, y, k, val;
int lowbit(int x){ return x & (-x); }
void update(int x, int k){
while(x <= n){
tree[x] += k;
x += lowbit(x);
}
}
int getsum(int x){
int sum = 0;
while(x){
sum += tree[x];
x -= lowbit(x);
}
return sum;
}
int query(int x, int y){
return getsum(y) - getsum(x - 1);
}
int main(){
ios :: sync_with_stdio(false);
cin >> n >> m;
for(int i = 1;i <= n;i ++){
cin >> val;
update(i, val); //更新即建树
}
while(m --){
cin >> op;
if(op == 1){
cin >> x >> k;
update(x, k);
}
else{
cin >> x >> y;
cout << query(x, y) << endl;
}
}
return 0;
}


树状数组 2:区间修改,单点查询

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 x xx

  2. 求出某一个数的值。

输入格式

第一行包含两个整数 N NNM MM,分别表示该数列数字的个数和操作的总个数。

第二行包含 N NN 个用空格分隔的整数,其中第 i ii 个数字表示数列第 $i $ 项的初始值。

接下来 M MM 行每行包含 2 224 44个整数,表示一个操作,具体如下:

操作 1 11: 格式:1 x y k 含义:将区间 [ x , y ] [x,y][x,y] 内每个数加上 k kk

操作 2 22: 格式:2 x 含义:输出第 x xx 个数的值。

输出格式

输出包含若干行整数,即为所有操作 2 22 的结果。

样例

样例输入

5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4

样例输出

6
10

提示

样例 1 解释

故输出结果为 6、10。


数据规模与约定

对于 30 % 30\%30% 的数据:N ≤ 8 N\le8N8M ≤ 10 M\le10M10

对于 70 % 70\%70% 的数据:N ≤ 10000 N\le 10000N10000M ≤ 10000 M\le10000M10000

对于 100 % 100\%100% 的数据:1 ≤ N , M ≤ 500000 1 \leq N, M\le 5000001N,M5000001 ≤ x , y ≤ n 1 \leq x, y \leq n1x,yn,保证任意时刻序列中任意元素的绝对值都不大于 2 30 2^{30}230


区间更新

差分

其实对于前面的两种操作,发现用的还是一个前缀和的思想,为了便于求出 [L,R] 区间的累加和,通过

树状数组求出两个前缀和后作差便轻易得出。那么按照同样的思路,为了便于对整个区间进行修改,可

以考虑差分的思想,使树状数组存的是关于原数组a(虽然不会用上)的差分,即
tree[i] = a[i] - a[i - 1]
于是每次在对区间更新时只需将相关的父子结点 “打上标记” 便实现,而要查询某个点i,只需求一

遍前缀和(a[i] = tree[1] + tree[2] + .. + tree[i])即可。

void insert(ll i, ll k){ //差分数组上打标记
while (i <= n){
tree[i] += k;
i += lowbit(i);
}
}
void update(ll x, ll y, ll k){
insert(x, k), insert(y + 1, -k);
}

单点查询

ll query(ll x){ //求前缀和,跟getsum一样
ll sum = 0;
while(x){
sum += tree[x];
x -= lowbit(x);
}
return sum;
}


C++代码

#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 4000010;
ll tree[N]; //实际上不会用到原数组a,两个操作的对象均是树,所以可以直接考虑存一个树
ll op, n, m, x, y, k, val;
ll lowbit(ll x){ return x & (-x); }
void insert(ll i, ll k){ //差分数组上打标记
while (i <= n){
tree[i] += k;
i += lowbit(i);
}
}
void update(ll x, ll y, ll k){
insert(x, k), insert(y + 1, -k);
}
ll query(ll x){ //求前缀和,跟getsum一样
ll sum = 0;
while(x){
sum += tree[x];
x -= lowbit(x);
}
return sum;
}
int main(){
ios :: sync_with_stdio(false);
cin >> n >> m;
for(int i = 1;i <= n;i ++){
cin >> val;
update(i, i, val); //更新即建树
}
while(m --){
cin >> op;
if(op == 1){
cin >> x >> y >> k;
update(x, y, k);
}
else{
cin >> x;
cout << query(x) << endl;
}
}
return 0;
}


对于区间修改和区间查询,很适合考虑线段树来做,??
线段树(区间修改,区间合并)


推荐阅读
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
author-avatar
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有