在解题的过程中,我们想维护一个数组的前缀和s[i] = A[1] + A[2] +…+A[i]。我们改变任意一个A[i],那么S[i]之后都会发生变化,朴素写法调整前缀和S最坏的情况需要O(n)的时间。所以引入树状数组,它的修改和求和都是O(logn)的,效率非常高。

一、基本思想


根据任意正整数关于2的不重复次幂的唯一分解性质,若一个正整数x的二进制表示为10101,其中等于1 的位置是0,2,4,那么正整数x可以被“二进制分解”成2 ^ 4 + 2 ^ 2 + 2^0。区间[1,x]可以被分成O(logx)个小区间。
长度为2 ^ 4的小区间[1,2 ^ 4]。
长度为2 ^ 2的小区间[2 ^ 4+1,2 ^ 4+2 ^ 2]。
长度为2 ^ 0的小区间[2 ^ 4+2 ^ 2 + 2,2 ^ 4+2 ^ 2 + 2 ^ 0]。

对于给定序列A,我们建立一个数组从,其中c[x]保存序列A的区间[x-lowbit(x)+1,x]中所有的和。
形成下图的树形结构:

从图中可以得到C[]和A[]关系:C[i]=A[i-2^ k+1]+A[i-2^k+2]+…A[i]; (k为i的二进制中末尾0的数量)
该结构满足一下的性质:

  • 1.每个内部结点C[x]保存以它为根的子树中所有叶结点的和。
  • 2.每个内部结点C[x]的子节点个数等于lowbit(x)的位数。
  • 3.除树根外,每个内部节点C[x]的父结点是C[x + lowbit(x)].
  • 4.树的深度为O(logN)。

二、算法操作


由于之前已经整理过树状数组原理的博客:https://blog.csdn.net/sinat_40872274/article/details/97895705
所以直接给出重要操作。
例如i=8时,k=3;

1.求lowbit(x)

lowbit(x)表示取出x的最低位1 换言之 lowbit(x)=2^k ( k为k为x的二进制中末尾0的数量)。
lowbit(x) = x&(-x)是怎么来的呢?具体分析一下

设x > 0,x的第k位上是最低位的1,后边还有若干个0。
先把x取反,此时第k位变成0,第0~到k-1位都是1。
再令x = x +1,此时因为进位,第k位变成1,第0~k-1位都是0.在上面的取反加1的 操作后,x的第k+1位到最高位因为取反,所以是与原来相反的,这样一来x&( ~ x+1)运算过后就只有第k位上是1了。而补码 ~x = -x-1 ,因此lowbit(x) = x&(-x)。
代码:


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

2.对某个元素进行加法操作

树状数组支持单点增加,意思是给序列中的某个数A[x]加上y,同时正确维护序列的前缀和,根据上面给出的树形结构和它的性质,只有结点C[x]及其所有祖先你结点保存的“区间和”包含结点A[x],而任意一个结点的祖先至多只有logN个,所以逐一对包含A[x]的C[]值进行更新就可以了,这样可以在O(logN)时间内执行单点增加操作。
那么我们的现在就是要解决如和找到A[x]的祖先结点。这就要回顾这个树形结构的第三点性质了。

  • 3.除树根外,每个内部节点C[x]的父结点是C[x + lowbit(x)]。

代码:

void update(int x,int y){for(;x<= N; x += lowbit(x)) c[x] += y;return ans;
}

3.查询前缀和

树状数组支持查询前缀和,即序列A第1~x个数的和。按照我们刚才提出的方法,应该求出x的二进制表示中每个等于1的位,把[1,x]分成O(logN)个小区间,而每个小区间的和都已经存在数组C[]中了,所以直接求代表每个小区间的C[]的和就行了。

int sum(int x){int ans = 0;for(; x; x -= lowbit(x)) ans += c[x];return ans;//可以发现求前缀和与加法操作 是两个逆序的过程
}

4.统计A[x]…A[y]的值

前缀和思想:sum(y) - sum(x-1)

5.多维树状数组

将一维的树状数组变成m维的,那么时间复杂度就是O((logN)^m),在m不大的时候,还是可以接受的。扩充的方法就是将原来的修改和查询函数中的一个循环,改成m个循环m维数组c中的操作。下面以n*m的二维数组a,树状数组c为例,给出单点增加和求和操作的代码。

//单点增加
void update(int x,int y ,int z){//将(x,y)的值加上zint i = x;while(i <= n){int j = y;while(j <= m){c[i][j] += z;j += lowbit(j);}i += lowbit(i);}
}
//求前缀和
int sum(int x,int y){int ans = 0,i = x;while(i > 0){int j = y;while(j > 0){ans += c[i][j];j -= lowbit(j);}i -= lowbit(i);}return ans;
}

6.注意事项

树状数组可以处理的是下标从1开始的数组,不能出现下标为0的情况,因为lowbit(0) = 0,会导致死循环。

【例题1】 一本通 OJ 1535:数列操作
题意
给定一个有n个数的数列,规定有两种操作,一是修改某个元素,二是求子数列[a,b]的连续和。数列元素个数最多10万个,询问操作最多10万次。
思路
定义模板题,由于查询次数大,所以树状数组相较于线段树更好一些。

#include <bits/stdc++.h>using namespace std;
const int Max = 1e6 + 10;
int n,m;
int a[Max] = {0},c[Max] = {0};
int lowbit(int x){return x&(-x);
}
void update(int x,int y){for(; x <= n; x += lowbit(x)) c[x] += y;
}
int sum(int x){int ans  = 0;for(; x; x -= lowbit(x)) ans += c[x];return ans;
}
int main(){scanf("%d %d",&n,&m);for(int i = 1; i<= n; i++) {scanf("%d",&a[i]);update(i,a[i]);}int k,x,y;while(m--){scanf("%d %d %d",&k,&x,&y);if(k == 0){printf("%d\n",sum(y) - sum(x-1));}else{update(x,y);}}return 0;
}

【例题2】 一本通OJ 1536:数星星 Stars (Ural 1028 )
题意
给出n个点的坐标,找出这n个点的左边,下边和左下共有几个点。
思路
看到二维坐标,感觉像是二维的树状数组,但由于点是按y从小到大给出的,所以我们只考虑点的x坐标就可以,只要在第i点前给出的点的x 小于等于i点的x,那么这个点就是合法的。

设a[i]为横坐标x为i-1的点的个数,要是找第k个点的合法点有多少,那么就是a[1]…a[k+1]的和,因为我们使用树状数组下标是不可以从0开始的,但是点的x坐标是从0开始的,所以采用这种x+1的方法。

#include <bits/stdc++.h>
using namespace std;const int Max = 33000;
int a[Max],c[Max],cnt_star[Max],xi[Max],yi[Max];
int n;
int lowbit(int x){return x&-x;
}
void update(int x,int y){for(; x <= Max; x += lowbit(x)) c[x] += y;
}
int sum(int x){int ans = 0;for(; x; x -= lowbit(x)) ans += c[x] ;return ans;
}
int main(){scanf("%d",&n);for(int i = 1; i <= n; i++){scanf("%d %d",&xi[i],&yi[i]);}for(int i = 1; i <= n; i++){int res = sum(xi[i]+1);update(xi[i]+1,1);cnt_star[res]++;}for(int i = 0; i < n; i++)printf("%d\n",cnt_star[i]);}

【例题3】1537 校门外的树(Vijos P1448)
题意
学校在某个时刻在某一段种上一种树,每次种的树种类不同,给出一个区间[l,r]问这个区间里有多少种树。
思路
这个问题的重点应该在找到每次种树时的起点和终点。
一本通书上给了一个有趣的比喻,对于插入的线段[li,ri]把数轴上li处看成一个‘(’表示开始种,ri处看成一个‘)’表示种结束了
对于询问区间[lq,rq]:
rq左边’('的个数就表示从开头到rq中树的种类数。
lq左边‘)’的个数就表示有多少种树没有种在询问区间[lq,rq]中,我们因此要减去这些种类。
可以发现,rq左边的‘(’的个数 减去 lq左边‘)’的个数 就是我们需要的答案。仔细想想,就是要求‘(’和‘)’的前缀和,然后相减。

拿样例举个例子:


前缀和 ,区间,对点修改 ,多次查询区间 ,所以想到树状数组。

  • 1.建立两个树状数组T1和T2分别维护‘(’和‘)’。
  • 2.若k = 1,读入li,ri,updateT1(li,1),updateT2(ri,1)。
  • 3.若k = 2,读入lq,rq sumT1(rq) - sumT2(lq-1)。
#include<bits/stdc++.h>
using namespace std;const int Max = 5e4 + 10;
int T1[Max],T2[Max];
int n,m;
int lowbit(int x){return x & -x;
}
void update(int x,int y,int T[]){for(; x <= n; x += lowbit(x)) T[x] += y;
}
int sum(int x,int T[]){int res = 0;for(; x; x -= lowbit(x)) res += T[x];return res;
}
int main(){scanf("%d %d",&n,&m);int k,l,r;for(int i = 1; i <= m; i++){scanf("%d %d %d",&k,&l,&r);if(k == 1){update(l,1,T1);update(r,1,T2); }else{int ans = sum(r,T1) - sum(l-1,T2);printf("%d\n",ans); }}return 0;
}

练习题:一本通OJ

1538 清点人数
1539 简单题
1540 打鼹鼠_二维树状数组

本文完全照抄 参考黄新军 董永建《信息学奥赛一本通·提高》

快乐地打牢基础(4)——树状数组相关推荐

  1. 树状数组萌新讲解+基础习题【一点一滴】

    树状数组基础篇 树状数组讲点 中文名:树状数组 英文名:Binary Indexeds Tree 英译中:二进制索引树 这特么多清楚 引入: 给你n个数 1. 求区间的的和 2. 改变某个值 然后朴素 ...

  2. 树状数组 区间update/query

    Re [问题引入] 对于区间修改.区间查询这样的简单问题,打一大堆线段树确实是不划算,今天来介绍一下区间查询+区间修改的树状数组 [一些基础] 树状数组的基本知识不再介绍,请自行百度 我们假设sigm ...

  3. 兰州大学第一届 飞马杯 ★★快乐苹果树★★ 树链剖分 + 懒标记 + 树状数组

    传送门 文章目录 题意: 思路: 题意: 思路: 第一次听说树链剖分能在fa[top[i]]fa[top[i]]fa[top[i]]的地方加懒标记,学到了学到了. 首先不能被题目吓住,这个题目仔细剖析 ...

  4. 2023牛客寒假算法基础集训营4_20230130「向上取整」「夹逼dp」「lowbit科学+树状数组性质」「搜索」「倍增跳表」「莫队」

    6/13 教育场是有被教育到.(预计会鸽几题. 已过非太水的题们 //B //https://ac.nowcoder.com/acm/contest/46812/B//小构造小数学#include & ...

  5. 树状数组基础原理与模板

    树状数组 2021年7月29 1.算法原理 树状数组解决什么问题? 解决区间上点更新与维护的问题.如更改某些点值求区间和,或求某位前有多少比其小的问题. 其实现的原理是什么? 首先看图: 在这个图中, ...

  6. 好骚气的树状数组的解释

    引用请注明出处:http://blog.csdn.net/int64ago/article/details/7429868 写下这个标题,其实心里还是没底的,与其说是写博帖,不如说是做总结.第一个接触 ...

  7. POJ 2299 Ultra-QuickSort(树状数组+离散化)

    题目大意: 就是说,给你一个序列,然后让你求出这个序列有多少个逆序对,所谓逆序对就是对于这个序列中的元素有a[i]>a[j] 且i<j存在. 其实原题是这样说的,给你一个序列,让你用最少的 ...

  8. BZOJ 2754 [SCOI2012]喵星球上的点名 (AC自动机、树状数组)

    吐槽: 为啥很多人用AC自动机暴力跳都过了?复杂度真的对么? 做法一: AC自动机+树状数组 姓名的问题,中间加个特殊字符连起来即可. 肯定是对点名串建AC自动机(map存儿子),然后第一问就相当于问 ...

  9. 树状数组求逆序对_区间和的个数(树状数组)

    327. 区间和的个数 给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper. 区间和 S(i, j) 表示在 nums 中,位置从 i ...

最新文章

  1. Nancy in .Net Core学习笔记 - 初识Nancy
  2. Gmail进程信息转储分析工具pdgmail
  3. 如果 “ 2X ”的补码是“ 90H ”,那么 X 的真值是( )。
  4. 经典面试|为何Kafka这么快?
  5. mysql 分区索引失效_分区表的本地索引竟然失效了——ORA-01502
  6. gvim最简化设置,去掉工具栏和菜单栏
  7. 接口规范 8. 播出认证相关接口
  8. 飞利浦医疗收购Direct Radiology,加强远程放射学服务能力
  9. java加载机制_详解Java类加载机制
  10. 安川机器人报错_安川机器人伺服驱动器常见的报警代码
  11. 2016新网商年度盛典,千机网解构新零售
  12. pyautogui 语法记录+休闲辅助工具实现-2021.12.15
  13. 数据库优化整理之:冷热分离
  14. win7计算机里没有网络图标,Win7网络图标不见了怎么办?Win7系统找回网络图标的方法...
  15. 在oracle数据库中存储数据库的文件是,Oracle数据库,oracle
  16. 如何实现大屏数字滚动效果
  17. Linux雷鸟邮件,thunderbird雷鸟mail
  18. 隐式图的搜索问题(九宫重排)——项目实现
  19. 5g信号频率是多少赫兹_1分钟看懂5G频谱(建议收藏)
  20. 记一次简单网络割接的经验教训

热门文章

  1. 绝地求生 服务器无响应,绝地求生点了开始没反应怎么办 绝地求生点开始没反应解决办法...
  2. NOI OJ 1.5 16:买房子 C语言
  3. ABAQUS几何非线性问题:薄板大变形(如何定义材料方向)
  4. ​S/4中究竟有多少个模块,你对这些模块了解多少
  5. 在c语言中调试作用是什么,调试技术在C语言程序设计教学中作用探讨.doc
  6. [Keras] 使用Keras调用多GPU时出现无法保存模型的解决方法
  7. ex计算机绘图基础教程怎么画图,计算机绘图基础教程(第2版)
  8. 全新开发悬赏任务系统源码分享
  9. Android手机怎么会越用越卡?真相就在这里
  10. 笔试题汇总一(网易)