【数据结构之树状数组】从零认识树状数组

一、关于树状数组

树状数组(Binary Indexed Tree,简称BIT),是一种修改和查询复杂度都为O(logN)的数据结构。但树状数组仅支持单点修改,在查询时,树状数组也要求被查询的区间具有可区间加减的性质。不过,树状数组由于代码实现容易、占用空间小,常用于代替线段树。

二、详解树状数组

在这里,我们定义原序列为a,树状数组为c,则有:

其中,k为i的二进制表示中末尾0的个数,例如:i=3(101)时,k=0;i=8(1000)时,k=3。

定义函数lowbit(x)=2k(k为x的二进制表示中末尾0的个数),利用机器补码的性质,得到:

1 int lowbit(int x)
2 {
3      return x&(-x);
4 }

根据定义,我们可以列出(这里我们用a[i,j]表示a[i]~a[j]间的所有元素的信息):

c[1]=a[1]

c[2]=a[1,2]

c[3]=a[3]

c[4]=a[1,4]

c[5]=a[5]

c[6]=a[5,6]

c[7]=a[7]

c[8]=a[1,8]

c[9]=a[9]

c[10]=a[9,10]

……

首先,由树状数组的定义,我们可以得到一个性质:

?a[x]在树状数组中第一次出现在c[x],并且c[x]包含的区间右端点为x

此外,通过观察,我们可以发现:

?a[x]仅对c[x]、c[x+lowbit(x)]、c[x+lowbit(x)+lowbit(x+lowbit(x))]……产生影响

类似于二进制拆分的思想,我们还可以发现:

?c[1]~c[x]可以还原出a[1]~a[x]的所有信息

所以,树状数组的空间复杂度就为O(n)。

那么,我们就可以写出维护树状数组的代码(这里我们的树状数组查询的信息为区间的和):

1 void update(int x,int val)
2 {
3      for(;x<=n;x+=lowbit(x))
4          c[x]+=val;
5      return;
6 }

接下来,如果我们要求区间a[i,j]的和,我们又要怎么做呢?

不妨这样想,a[i,j]的和其实等于a[1,j]的和减去a[1,i-1]的和,那么我们只需要知道如何求一个的序列前缀和就可以了。

首先,根据定义,我们知道:

?c[x]包含的区间长度为lowbit(x)

在这个性质下,与维护操作类似,我们可以很轻松地写出求前缀和的代码:

1 int query(int x)
2 {
3      int ans=0;
4      for(;x;x-=lowbit(x))
5          ans+=c[x];
6      return ans;
7 }

以上,就是树状数组的基本内容了,下面我们来看一道例题。

三、题目

Description

一行N个方格,开始每个格子里都有一个整数。现在动态地提出一些问题和修改:提问的形式是求某一个特定的子区间[a,b]中所有元素的和;修改的规则是指定某一个格子x,加上或者减去一个特定的值A。现在要求你能对每个提问作出正确的回答。1≤N<100000,,提问和修改的总数m<10000条。

Input Description

输入文件第一行为一个整数N,接下来是n行n个整数,表示格子中原来的整数。接下一个正整数m,再接下来有m行,表示m个询问,第一个整数表示询问代号,询问代号1表示增加,后面的两个数x和A表示给位置X上的数值增加A,询问代号2表示区间求和,后面两个整数表示a和b,表示要求[a,b]之间的区间和。

Output Description

共m行,每个整数

Sample Input

6

4

5

6

2

1

3

4

1 3 5

2 1 4

1 1 9

2 2 6

Sample Output

22

22

Data Size & Hint

1≤N≤100000, m≤10000 。

附上原题链接→_→|1080 线段树练习|CODEVS,算法爱好者社区

虽然是线段树练习,但在开头我们便讲过,树状数组在一定情况下可以替代线段树。

四、代码实现

以下内容是一个树状数组单点维护并求区间和的模板,可以通过CodeVs1080。其实核心代码都在上文出现过,这里只是做一个整理。

 1 #include<cstdio>
 2 const int MAXN=1e5+10;
 3 int n,m;
 4 int lowbit(int x){return x&(-x);}
 5 int BIT[MAXN];
 6 void update(int x,int val)
 7 {
 8     for(;x<=n;x+=lowbit(x))
 9         BIT[x]+=val;
10 }
11 int query(int x)
12 {
13     int ans=0;
14     for(;x;x-=lowbit(x))
15         ans+=BIT[x];
16     return ans;
17 }
18 int main()
19 {
20     scanf("%d",&n);
21     for(int i=1;i<=n;++i)
22     {
23         int a;
24         scanf("%d",&a);
25         update(i,a);
26     }
27     scanf("%d",&m);
28     for(int i=1;i<=m;++i)
29     {
30         int flag,l,r;
31         scanf("%d%d%d",&flag,&l,&r);
32         if(flag==1)update(l,r);
33         else
34         {
35             int ans=query(r)-query(l-1);
36             printf("%d\n",ans);
37         }
38     }
39     return 0;
40 }

CodeVs1080 线段树练习

弱弱地说一句,本蒟蒻码字也不容易,转载请注明出处http://www.cnblogs.com/Maki-Nishikino/p/6217811.html

时间: 2024-10-09 20:26:42

【数据结构之树状数组】从零认识树状数组的相关文章

【算法#3】树状数组&amp;二叉索引树

其实是数据结构. 智推连续几天给我推树状数组的模板,还放在第一位-- 对着蓝书的图看了好几天才看懂,树状数组的另外一个名字是二叉索引树,指通过把一个数组抽象的变形成树状的以求得到树形数据结构的效果.有人说是线段树的阉割版,我不太清楚,树状数组应该是不支持区间修改加速的. 首先我们需要理解lowbit的概念,它指的是一个数转成二进制后位数最低的那个1表示的值.它具有一个特殊的性质但是为什么具有这个性质是无须证明也不用了解的. 然后我们画一个图,在一定范围内按lowbit的大小从上向下逐层分布,一层

【Hihocoder 1167】 高等理论计算机科学 (树链的交,线段树或树状数组维护区间和)

[题意] 时间限制:20000ms 单点时限:1000ms 内存限制:256MB 描述 少女幽香这几天正在学习高等理论计算机科学,然而她什么也没有学会,非常痛苦.所以她出去晃了一晃,做起了一些没什么意义的事情来放松自己.门前有一颗n个节点树,幽香发现这个树上有n个小精灵.然而这些小精灵都比较害羞,只会在一条特定的路径上活动.第i个小精灵会在ai到bi的路径上活动.两个小精灵是朋友,当且仅当它们的路径是有公共点的.于是幽香想要知道,有多少对小精灵a和b,a和b是朋友呢?其中a不等于b,a,b和b,

POJ 1804 Brainman(5种解法,好题,【暴力】,【归并排序】,【线段树单点更新】,【树状数组】,【平衡树】)

Brainman Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 10575   Accepted: 5489 Description BackgroundRaymond Babbitt drives his brother Charlie mad. Recently Raymond counted 246 toothpicks spilled all over the floor in an instant just b

HDU 4417 Super Mario ( 超级马里奥 + 主席树 + 线段树/树状数组离线处理 + 划分树 )

HDU 4417 - Super Mario ( 主席树 + 线段树/树状数组离线处理 + 划分树 ) 这道题有很多种做法,我先学习的是主席树.后面陆续补上线段树离线和划分树 题目大意就是给定一个区间给定一个数列,每次要求你查询区间[L,R]内不超过K的数的数量 主席树做法: 最基本的是静态第k大,这里是求静态的 <= K,差不多,在Query操作里面需要修改修改 先建立size棵主席树,然后询问的时候统计的是 第R棵主席树中[1,K]的数量 - 第L-1棵主席树中[1,K]的数量 注意这里下标

【BZOJ】1146: [CTSC2008]网络管理Network(树链剖分+线段树套平衡树+二分 / dfs序+树状数组+主席树)

第一种做法(时间太感人): 这题我真的逗了,调了一下午,疯狂造数据,始终找不到错. 后来发现自己sb了,更新那里没有打id,直接套上u了.我.... 调了一下午啊!一下午的时光啊!本来说好中午A掉去学习第二种做法,噗 好吧,现在第一种做法是hld+seg+bst+二分,常数巨大,log^4级别,目前只会这种. 树剖后仍然用线段树维护dfs序区间,然后在每个区间建一颗平衡树,我用treap,(这题找最大啊,,,囧,并且要注意,这里的rank是比他大的数量,so,我们在二分时判断要判断一个范围,即要

树状数组求区间最大值(树状数组)(复习)

如题. 当遇到单点更新时,树状数组往往比线段树更实用. 算法: 设原数序列为a[i],最大值为h[i](树状数组). 1.单点更新: 直接更新a[i],然后再更新h[i].若h[i]的值有可能改变的,则表示区间一定包含i结点.那么就两层lowbit更新所有可能的h. 单点更新时间复杂度O(logn*logn) 1 void update(int x) 2 { 3 while(x<=n) 4 { 5 h[x]=a[x]; 6 for(int i=1;i<lowbit(x);i<<=1

Vijos P1448 校门外的树【多解,线段树,树状数组,括号序列法+暴力优化】

校门外的树 描述 校门外有很多树,有苹果树,香蕉树,有会扔石头的,有可以吃掉补充体力的…… 如今学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两个操作: K=1,K=1,读入l.r表示在区间[l,r]中种上一种树,每次操作种的树的种类都不同 K=2,读入l,r表示询问l~r之间能见到多少种树 (l,r>0) 格式 输入格式 第一行n,m表示道路总长为n,共有m个操作 接下来m行为m个操作 输出格式 对于每个k=2输出一个答案 样例1 样例输入1 5 4 1 1

poj 1195 二维树状数组 及二维树状数组模板

http://poj.org/problem?id=1195 求矩阵和的时候,下标弄错WA了一次... 求矩形(x1,y1) (x2,y2)的sum |sum=sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1) 二维树状数组讲解:http://blog.csdn.net/u011026968/article/details/38532117 二维树状数组模板: /*========================================

HDU 4325 离散化+树状数组 或者 不使用树状数组

题意:给出一些花的开放时间段,然后询问某个时间点有几朵花正在开放. 由于ti<1e9,我们需要先将时间离散化,然后将时间点抽象为一个数组中的点,显然,我们需要进行区间更新和单点查询,可以考虑线段树与树状数组两种做法,一般的,树状数组是用来维护区间和与单点修改的,那么,如何通过树状数组进行区间更新和单点查询呢,联想到差分数组,差分数组可以在o(1)的时间内进行区间的更新,但是单点查询的效率为o(n),显然不能用于处理此题,这时,考虑树状数组维护差分数组,就可以o(logn)地进行区间更新(更新差分

数组拷贝、数组函数、通过数组函数来模拟数据结构的栈和队列、回调的意义、数组函数的排序问题、算法以及寻找素数的筛选法

1.数组的拷贝数组拷贝时指针的指向问题. 数组在拷贝时,指针的位置随之复制[这一点拷贝是完全一样]但是如果拷贝的数组的指针是非法的,那么拷贝出新指针的位置进行初始化<?php$arr1=array('123');end($arr1);next($arr1);//这个指针非法$arr2 = $arr1;//这里进行数组的拷贝var_dump(current($arr2));//得到指向‘123’元素的指针var_dump(current($arr1));//此时这个数组的指针有问题?> 但是拷贝