树状数组 思路以及性能分析
特点
代码短、常数很小
应用及时间复杂度
- 区间查询:求前缀和
- 单点修改:给某个位置上的数加上一个数(同时能以非常小的代价维护前缀和)
时间复杂度:O(logn)O(logn)O(logn)
与一般前缀和算法的对比
算法 | 修改某个点 | 查询前缀和 | 平均时间复杂度(假定两种操作各占50%50\%50%) |
---|---|---|---|
一般前缀和 | O(n)O(n)O(n) | O(1)O(1)O(1) | O(n)O(n)O(n) |
树状数组 | O(logn)O(logn)O(logn) | O(logn)O(logn)O(logn) | O(logn)O(logn)O(logn) |
可以看出在修改和查询操作占比差不多时,树状数组的效率更高
那么什么时候用树状数组,什么时候用一般前缀和算法呢?
这就要明白这两个算法的本质区别:
一般前缀和算法是离线算法,它不支持动态的改变单个元素的值,或者说改变单个元素值后,重新维护前缀和所花费的代价很大。
树状数组是在线算法,支持动态改变单个元素的值,以很小的代价动态维护前缀和。
所以当仅仅需要用到前缀和,不涉及动态的改变单个元素的值时,首选一般前缀和算法,否则就用树状数组。
树状数组原理
树状数组图例(下标从111开始)
假设原序列为aaa,树状数组序列为ccc,那么是怎么由原序列得到树状数组序列的呢?(可以把ccc理解为aaa的前缀和序列,只是前缀和关系不像一般前缀和那样简单、线性)
- 首先,将一维的树状数组序列ccc看成多层的序列,c[i]c[i]c[i]属于第几层,取决于iii的二进制表示中最后一个111后面有几个000,有几个000就在第几层,显而易见,当iii为奇数时,c[i]c[i]c[i]是在第000层的
因为lowbit(x)=2klowbit(x)=2^{k}lowbit(x)=2k,kkk表示x的二进制表示后面有多少个0
(lowbit(n)lowbit(n)lowbit(n)求得n的二进制表示中最后一个1以及往后的0)
可以得到关系:
c[x]=a(x−lowbit(x),x]c[x]=a(x-lowbit(x),x]c[x]=a(x−lowbit(x),x]
此关系描述了序列ccc中每个元素是哪一段序列aaa中元素的和 - 如何通过树状数组求前缀和?
由上面公式知道,想要求序列aaa中111到xxx的和,则应该是:
∑i=1xai=c[x]+c[x−lowbit(x)]+......\sum_{i=1}^{x}a_i=c[x]+c[x-lowbit(x)]+......∑i=1xai=c[x]+c[x−lowbit(x)]+......
因而可得代码:
int res=0;
for(int i=x;i>0;i-=lowbit(x)) res+=c[i];
return res;
- 如何通过树状数组进行单点修改?
这里我们给出一个结论:一个结点a[i]a[i]a[i]或c[i]c[i]c[i]的父结点为c[i+lowbit(i)]c[i+lowbit(i)]c[i+lowbit(i)]
所以当我们改变a[i]a[i]a[i]的值时,依次递归向上更新父结点的值即可。
代码:
a[x]+=v;
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=v;
我们发现这里是给某个数加上一个数vvv,而不是把某个数变成vvv,如果想实现这样的效果,该怎么做呢?
我们可以把加的数vvv灵活的调整为v−xv-xv−x(假设原来的数为xxx),加上v−xv-xv−x之后原来的数xxx就变成vvv了,从而实现了让一个数变成一个给定的数的效果。
获得原来的数的方式:
- 树状数组前缀和相减:c[x]−c[x−1]c[x]-c[x-1]c[x]−c[x−1]
- 开一个数组存原数组
树状数组只能给一个数加上一个数,而不能把一个数变成一个数,要实现这样的操作,作上面的变换即可。
初始化树状数组
可以假设原序列aaa为全0,依次通过“单点修改”操作把每个数加进去,最后就可以形成树状数组了。
例题:AcWing 1264. 动态求连续区间和
#include<iostream>
using namespace std;
const int N=100010;
int a[N],c[N];
int n,m;
int k,x,y;
int lowbit(int x)
{return x&(-x);
}
void add(int x,int v)
{for(int i=x;i<=n;i+=lowbit(i)) c[i]+=v;
}
int query(int x)
{int res=0;for(int i=x;i>0;i-=lowbit(i)) res+=c[i];return res;
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) scanf("%d",&a[i]);for(int i=1;i<=n;i++) add(i,a[i]); //初始化while(m--){scanf("%d%d%d",&k,&x,&y);if(k==0) printf("%d\n",query(y)-query(x-1)); //前缀和思想else add(x,y); }return 0;
}
树状数组 思路以及性能分析相关推荐
- HDU 4638 Group 树状数组 + 思路
实际上就是问这个区间编号连续的段的个数,假如一个编号连续的段有(a+b)个人,我把他们分在同一组能得到的分值为(a+b)^2,而把他们分成人数为a和b的两组的话,得到的分值就是a^2+b^2,显然(a ...
- 树状数组(详细分析+应用),看不懂打死我!
树状数组介绍 在学习一个算法之前一定要清楚它能干嘛,能解决什么样的问题,对你解题是否有帮助,然后才去学习它! 那么接下来看如下几个问题 什么是树状数组 顾名思义就是一个结构为树形结构的数组,于二叉树的 ...
- 如此好的树状数组学习资料
树状数组学习系列1 之 初步分析--czyuan原创 其实学树状数组说白了就是看那张图,那张树状数组和一般数组的关系的,看懂了基本就没问题了,推荐下面这个教程:http://www.topcoder. ...
- AcWing 蓝桥杯AB组辅导课 05、树状数组与线段树
文章目录 前言 一.树状数组 1.1.树状数组知识点 1.2.树状数组代码模板 模板题:AcWing 1264. 动态求连续区间和 例题 例题1.AcWing 1265. 数星星[中等,信息学奥赛一本 ...
- [算法模板]树状数组
[算法模板]树状数组 思路 图片转自:yhf_2015--彻底理解树状数组 使用这个图片就能很快的理解树状数组. 我们可以先根据图片来分解一个十进制数成二次幂. example: \(15=2^0+1 ...
- E. String Reversal (树状数组)
E. String Reversal (树状数组) 思路:树状数组 考虑每个位置的贡献,从最后一个字符开始计算. 显然对于相同的字符选取靠前的位置的贡献更小. 所以用队列维护相同字母的位置. 从后往前 ...
- 1010 Lehmer Code (35 分)(思路+详解+树状数组的学习+逆序对+map+vector) 超级详细 Come baby!!!
一:题目 According to Wikipedia: "In mathematics and in particular in combinatorics, the Lehmer cod ...
- poj 2352 Stars 线段树(先建后查/边建边查)/树状数组三种方法思路详解,带你深入了解线段树难度⭐⭐⭐★
poj 2352 Stars 目录 poj 2352 Stars 1.树状数组 2.线段树,先建树后查找 3.线段树,边建树边查找 Description Astronomers often exam ...
- HDU 4358 树状数组+思路
http://acm.hdu.edu.cn/showproblem.php?pid=4358 如图所示,当k==3时,如果我们扫描到红线所在的位置. 则符合条件的区间就是从红线到两条紫线所包含的区间( ...
最新文章
- 你知道Java文件拷贝有几种方式么?
- C++ 构造函数的初始化列表
- linux 如何运行r脚本,Linux系统下如何debug R脚本
- 真实临床“生态”下实效性研究的挑战和意义
- 希尔排序-Java二
- windows环境下 curl 安装和使用
- c语言判定三角形方法,c语言判定三角形的各种类型——请大家指点
- AGC005D - ~K Perm Counting(组合数学,背包,dp)
- CSP2021NOIP2021游记
- java comparator_【面试题】Java必考面试题全集(15)
- 一个程序员的逗逼瞬间(二)
- 记录下最近使用到的sql语句
- 【转】Pro Android学习笔记(八):了解Content Provider(下中)
- 15 —— npm —— package.json 与 package-lock.json 的作用
- 个人Blog小程序开发完毕
- EXT Column Tree 的应用
- java程序如何安装到手机上_java怎么安装到手机?手机安装java的教程
- 联想计算机 经常蓝屏怎么办,一分钟看懂电脑蓝屏(内附解决方案)
- iframe背景色透明
- 直连路由、主机路由以及选择顺序