BIT 树状数组 详解 及 例题

(一)树状数组的概念

如果给定一个数组,要你求里面所有数的和,一般都会想到累加。但是当那个数组很大的时候,累加就显得太耗时了,时间复杂度为O(n),并且采用累加的方法还有一个局限,那就是,当修改掉数组中的元素后,仍然要你求数组中某段元素的和,就显得麻烦了。所以我们就要用到树状数组,他的时间复杂度为O(lgn),相比之下就快得多。下面就讲一下什么是树状数组:

一般讲到树状数组都会少不了下面这个图:

下面来分析一下上面那个图看能得出什么规律:

据图可知:c1=a1,c2=a1+a2,c3=a3,c4=a1+a2+a3+a4,c5=a5,c6=a5+a6,c7=a7,c8=a1+a2+a3+a4+a5+a6+a7+a8,c9=a9,c10=a9+a10,c11=a11........c16=a1+a2+a3+a4+a5+.......+a16。

分析上面的几组式子可知,当 i 为奇数时,ci=ai ;当 i 为偶数时,就要看 i 的因子中最多有二的多少次幂,例如,6 的因子中有 2 的一次幂,等于 2 ,所以 c6=a5+a6(由六向前数两个数的和),4 的因子中有 2 的两次幂,等于 4 ,所以 c4=a1+a2+a3+a4(由四向前数四个数的和)。

(一)有公式:cn=a(n-a^k+1)+.........+an(其中 k 为 n 的二进制表示中从右往左数的 0 的个数)。

那么,如何求 a^k 呢?求法如下:

1 int lowbit(int x) //取x的最低位1,比如4,则返回4,如5,则返回1
2 {
3     return x&(-x);
4 }

lowbit()的返回值就是 2^k 次方的值。

求出来 2^k 之后,数组 c 的值就都出来了,接下来我们要求数组中所有元素的和。

(二)求数组的和的算法如下:

(1)首先,令sum=0,转向第二步;

(2)接下来判断,如果 n>0 的话,就令sum=sum+cn转向第三步,否则的话,终止算法,返回 sum 的值;

(3)n=n - lowbit(n)(将n的二进制表示的最后一个零删掉),回第二步。

代码实现:

 1 int Sum(int i)   //求前i项的和
 2 {
 3     int s = 0;
 4     //将前i项分段
 5     while(i > 0)
 6     {
 7         s += sum[i];
 8         i -= lowbit(i);  //去掉i的二进制最后一个
 9     }
10     return s;
11 }

(三)当数组中的元素有变更时,树状数组就发挥它的优势了,算法如下(修改为给某个节点 i 加上 x ):

(1)当 i<=n 时,执行下一步;否则的话,算法结束;

(2)ci=ci+x ,i=i+lowbit(i)(在 i 的二进制表示的最后加零),返回第一步。

代码实现:

1 void update(int i, int val)  //将第i个元素增加val
2 {
3     //i的祖先都要增加val
4     while(i <= n)
5     {
6         sum[i] += val;
7         i += lowbit(i);   //将i的二进制未位补为得到其祖先
8     }
9 }

(二)树状数组的应用

以下数组下标均默认从1开始

应用一

假如给你一个数组a[ ] = {2,5,3,4,1},求b[i],b[i] 表示在a[1],a[2]...a[i-1]中(即位置i的左边)小于等于a[i]的数的个数。对此例b[] = {0,1,1,2,0}。 那么该如何去求得b[i]呢?

解法:假如要得到b[4]的值,对于a[4] = 4. 我们 只要得到在a[1],a[2],a[3] 中出现小于等于4的个数,即1,2,3,4的个数,此例即为2. a[1] = 2 < a[4], a[3] = 3 < a[4]. 所以b[4] = 2;其他的以此类推. 求b[i]的值,需要得到在a[1],a[2]....a[i-1]中出现小于等于a[i]的个数,即1,2...a[i]的个数. 相当于求前a[i]项的和,可用到树状数组.

具体操作

for(int i=1; i<=n; i++)

{

b[i] = getSum(a[i]); //求前a[i]项的和

update(a[i],1);      //第a[i]个元素+1

}

应用二

假如给你一个数组a[ ] = {2,5,3,4,1},求b[i],b[i] 表示在a[1],a[2]...a[i-1]中(即位置i的左边)大于等于a[i]的数的个数。对此例b[] = {0,0,1,1,4}。 那么该如何去求得b[i]呢?

解法1: 只需要先将数组a倒过来编号,即将a转换为,a[] ={4,1,3,2,5}.此时具体的操作如应用一

以下数组下标均默认从1开始

应用一

假如给你一个数组a[ ] = {2,5,3,4,1},求b[i],b[i] 表示在a[1],a[2]...a[i-1]中(即位置i的左边)小于等于a[i]的数的个数。对此例b[] = {0,1,1,2,0}。 那么该如何去求得b[i]呢?

解法:假如要得到b[4]的值,对于a[4] = 4. 我们 只要得到在a[1],a[2],a[3] 中出现小于等于4的个数,即1,2,3,4的个数,此例即为2. a[1] = 2 < a[4], a[3] = 3 < a[4]. 所以b[4] = 2;其他的以此类推. 求b[i]的值,需要得到在a[1],a[2]....a[i-1]中出现小于等于a[i]的个数,即1,2...a[i]的个数. 相当于求前a[i]项的和,可用到树状数组.

具体操作

for(int i=1; i<=n; i++)

{

b[i] = getSum(a[i]); //求前a[i]项的和

update(a[i],1);      //第a[i]个元素+1

}

应用二

假如给你一个数组a[ ] = {2,5,3,4,1},求b[i],b[i] 表示在a[1],a[2]...a[i-1]中(即位置i的左边)大于等于a[i]的数的个数。对此例b[] = {0,0,1,1,4}。 那么该如何去求得b[i]呢?

解法1: 只需要先将数组a倒过来编号,即将a转换为,a[] ={4,1,3,2,5}.此时具体的操作如应用一

解法2:改变更新路径和求和路径

 1 void update(int x, int val)
 2 {
 3     for(int i=x; i>0; i-=lowbit(i))
 4     {
 5         sum[i] += val;
 6     }
 7 }
 8
 9 int getSum(int x)
10 {
11     int s = 0;
12     for(int i=x; i<MAXN; i+=lowbit(i))
13     {
14         s += sum[i];
15     }
16     return s;
17 }

应用三  逆序数

假如给你一个数组a[ ] = {2,5,3,4,1},求b[i],b[i] 表示在a[i],a[i+1]...a[n]中(即位置i的右边)小于等于a[i]的数的个数。对此例b[] = {1,3,1,1,0}。 那么该如何去求得b[i]呢?

操作:应用一位置i的左边,应用三是位置i的右边。 然后只需要在应用一的基础上从后往前操作即可

1 for(int i=n; i>=1; i--)
2
3 {
4
5 b[i] = getSum(a[i]); //求前a[i]项的和
6
7 update(a[i],1);      //第a[i]个元素+1
8
9 }

应用四

假如给你一个数组a[ ] = {2,5,3,4,1},求b[i],b[i] 表示在a[i],a[i+1]...a[n]中(即位置i的右边)大于等于a[i]的数的个数。对此例b[] = {3,0,1,0,0}。 那么该如何去求得b[i]呢?

操作:只需将数组a倒过来编号,即将a转化为 a[]={4,1,3,2,5} 然后利用应用三

二维树状数组

 1 int lowbit(int x)
 2 {
 3     return x&(-x);
 4 }
 5
 6 void update(int x, int y, int val) //将 a[x][y] 的值增加val
 7 {
 8     for(int i=x; i<N; i+=lowbit(i))
 9     {
10         for(int j=y; j<N; j+=lowbit(j))
11         {
12             sum[i][j] += val;
13         }
14     }
15 }
16
17
18 int getSum(int x, int y) //求以1,1为左上角端点,学校,x,y为右下角端点的矩阵和.
19 {
20     int s = 0;
21     for(int i=x; i>0; i-=lowbit(i))
22     {
23         for(int j=y; j>0; j-=lowbit(j))
24         {
25             s += sum[i][j];
26         }
27     }
28     return s;
29 }

(三)例题

基础应用

HDU 1166 敌兵布阵(树状数组)

http://www.cnblogs.com/ws5167/p/3904004.html

HDU 2689 Sort it (树状数组)

http://www.cnblogs.com/ws5167/p/3915614.html

HDU 2492 Ping pong (树状数组)

HDU Cow Sorting (树状数组)

二维树状数组

HDU1559 最大子矩阵 (二维树状数组)

HDU 1892 See you~ (二维树状数组)

三维树状数组

HDU 3584 Cube (三维 树状数组)

树状数组求逆序数

HDU 1394 Minimum Inversion Number ( 树状数组求逆序数 )

DP+树状数组+离散化

HDU 2227 Find the nondecreasing subsequences (DP+树状数组+离散化)

 

BIT 树状数组 详解 及 例题,布布扣,bubuko.com

时间: 2024-10-18 22:35:51

BIT 树状数组 详解 及 例题的相关文章

高级数据结构:优先队列、图、前缀树、分段树以及树状数组详解

优秀的算法往往取决于你采用哪种数据结构,除了常规数据结构,日常更多也会遇到高级的数据结构,实现要比那些常用的数据结构要复杂得多,这些高级的数据结构能够让你在处理一些复杂问题的过程中多拥有一把利器.同时,掌握好它们的性质以及所适用的场合,在分析问题的时候回归本质,很多题目都能迎刃而解了. 这篇文章将重点介绍几种高级的数据结构,它们是:优先队列.图.前缀树.分段树以及树状数组. 一.优先队列 1.优先队列的作用 优先队列最大的作用是能保证每次取出的元素都是队列中优先级别最高的,这个优先级别可以是自定

树状数组详解(图形学算法)

目录 一.从图形学算法说起 1.Median Filter 概述 2.r pixel-Median Filter 算法 3.一维模型 4.数据结构的设计 5.树状数组华丽登场 二.细说树状数组 1.树 or 数组? 2.结点的含义 3.求和操作 4.更新操作 5.lowbit函数O(1)实现 6.小结 三.树状数组的经典模型 1.PUIQ模型 2.IUPQ模型 3.逆序模型 4.二分模型 5.再说Median Filter 6.多维树状数组模型 四.树状数组题集整理 一.从图形学算法说起 1.M

树状数组详解

一.引入和概念 平常我们会遇到一些对数组进行维护查询的操作,比较常见的,修改某点的值.求某个区间的和. 数据规模不大的时候,对于修改某点的值是非常容易的,复杂度是O(1),但是对于求一个区间的和就要扫一遍了,复杂度是O(N). 如果实时的对数组进行M次修改或求和,最坏的情况下复杂度是O(M*N),当规模增大后这是划不来的. 而树状数组干同样的事复杂度却是O(M*lgN). 树状数组是一个查询和修改复杂度都为log(n)的数据结构. 主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素

树状数组略解

树状数组 树状数组是一个很奇特的树,它的节点会比线段树少一些,也能表示一个数组. 比如一个数组叫做a有8个数,那么它的树状数组样子就长这样 c数组就是树状数组,能看出来 c1=a1; c2=a1+a2; c3=a3; c4=a1+a2+a3+a4; 以此类推...... 很难说出他们的关系,但是如果把它们变为二进制 c0001=a0001 c0010=a0001+a0010 c0011=a0011 c0100=a0001+a0010+a0011+a0100 你会发现,将每一个二进制,去掉所有高位

oracle树状索引详解(图摘取《收获不止oracle》)

一.树状索引特点 1.高度较低 2.存储列值 3.结构有序 我们先看一下索引的结构,如图: 以上结构图说明索引是由 ROOT(根块),Branch(茎块)和Leaf(叶子块)三部分组成的,其中最底层的叶子块 主要存储了 key column value(索引列具体值),以及能具体定位到数据所在位置的rowid(此处rowid和查询时候用的rownum不是同一个概念,有兴趣可以百度rownum和rowid的区别) 注意点:索引块和数据块 需要区分,索引块也是占磁盘空间的 二.oracle索引查询

树状数组模板1(例题洛谷P3374)(单点修改+区间查询)

例题:https://www.luogu.org/problem/show?pid=3374 程序: #include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n,m,x,y,ch,f[600000],a[600000]; int lowbit(int x) { return (x&-x); } //单点修改 void update(int x,int k)

一维 + 二维树状数组 + 单点更新 + 区间更新 详解

树状数组详解: 假设一维数组为A[i](i=1,2,...n),则与它对应的树状数组C[i](i=1,2,...n)是这样定义的: C1 = A1 C2 = A1 + A2 C3 = A3 C4 = A1 + A2 + A3 + A4 C5 = A5 C6 = A5 + A6 ................. C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 ................ 如图可知: 为奇数的时候他是代表他本身,而为偶数的时候则是代表着自

初学树状数组

原理: 有好的博客做讲解了(见参考文章),这里暂时略过,如果以后有新的理解和体会会再来写的.(应该不会) 思想: 这里可以把树状数组的精妙之处提一下(我理解的) 首先,树状数组之所以叫树状数组,因为它像树一样,有类似树的父子节点关系,这点在更新和求和操作上体现的最为明显.而最终也只是数组,因为实现起来简单方便,如数组一样.(一开始还纳闷为什么不叫二进制索引树),英文名BIT(Binary Index Tree).这个数据结构实现的功能像线段树一样,两者有着异曲同工之妙. 其次,树状数组的神奇之处

浅析树状数组

目录 beginning 顺序结构 A+B 高精 A+B 压位高精 A+B 二分A+B 树状数组简介(不喜欢啰嗦的请直接跳到这里) 基础概念 代码实现 大体结构 lowbit lowbit的作用 总结+代码 逆序对 离散化 方式 实现 代码 树状数组进阶 差分 区间修改+单点查询 主要思想 单点查询 区间修改 区间修改+区间查询 差分分析 代码 2D树状数组 query update 区间修改+单点查询 区间修改+区间查询 时间复杂度 罗列例题 一维 beginning 顺序结构 A+B 高精