树状数组及其应用
树状数组及其应用

树状数组及其应用(Binary Indexed Trees)一、什么是树状数组【引例】假设有一列数{A i}(1<=i<=n),支持如下两种操作:1.将A k的值加D。
(k,D是输入的数)2.输出A s+A s+1+…+A t(s,t都是输入的数,s<=t)分析一:线段树建立一颗线段树(线段长度1~n)。
一开始所有结点的count值等于0。
对于操作1,如果把A k的值加D,则把所有覆盖了A k的线段的count值加D。
只有log2n 条线段会受到影响,因此时间复杂度是O(log2n)。
每条线段[x..y]的count值实际上就是A x+A x+1+…+A y的值。
对于操作2,实际上就是把[s..t]这条线段分解成为线段树中的结点线段,然后把所有的结点线段的count值相加。
该操作(ADD操作)在上一讲线段树中已介绍。
时间复杂度为O (log2n)。
分析二:树状数组树状数组是一种特殊的数据结构,这种数据结构的时空复杂度和线段树相似,但是它的系数要小得多。
增加数组C,其中C[i]=a[i-2^k+1]+……+a[i](k为i在二进制形式下末尾0的个数)。
由c数组的定义可以得出:i K1(1)201-2^0+1=1…1c[1]=a[1]2(10)212-2^1+1=1…2c[2]=a[1]+a[2]=c[1]+a[2]3(11)203-2^0+1=3…3c[3]=a[3]4(100)224-2^2+1=1…4c[4]=a[1]+a[2]+a[3]+a[4]=c[2]+c[3]+a[4] 5(101)205-2^0+1=5…5c[5]=a[5]6(110)216-2^1+1=5…6c[6]=a[5]+a[6]=c[5]+a[6]………………为了对树状数组有个形象的认识,我们先看下面这张图。
如图所示,红色矩形表示的数组C[]就是树状数组。
我们也不难发现,这个k就是该节点在树中的高度,因而这个树的高度不会超过logn。
树状数组及其应用双语版

• 对于询问(x1,y1)-(x2,y2),
ans=getsum(x2,y2)-getsum(x2,y1-1)-getsum(x11,y2) +getsum(x1-1,x2-1);
树状数组下标必须从1开始
Superbrother 神牛
• 到此,我们已经学习完树状数组的基本内 容。 • 树状数组的应用非常广泛,变形极多,灵 活性强,很多题目经过一系列转化后可以 使用树状数组解决。 • 下面,我将通过其他几个例题介绍如何通 过有效的转化使用树状数组解题。
样例输出 100
• 此题条件简单,但并不直观 • 将区间坐标化,我们发现,对于每头牛, 要求的就是其左上方的牛的个数。 • 同stars,注意判断点重合的情况
4、逆序对
• 题目大意 • 给定一个序列a[1]..a[n],对于任意i,j,如果i<j 并且a[i]>a[j],我们说这是一个逆序对。 • 你的任务是输出逆序对的个数 • N<=100000 a[i]<=maxlongint
Function getsum(x,y):integer;(求出矩阵(1,1)~(x,y)点值 和) Var z,t:longint; Begin t:=0; while x>0 do begin z:=y; while z>0 do begin t:=t+c[x,z]; z:=z-lowbit(z); end; x:=x-lowbit(x); end; getsum:=t; End;
• 此题是树状数组的经典应用 • 首先离散化坐标使数据范围减小,为使用 树状数组创造了条件 • 按横坐标排序,使得原题中“左下方”两 个条件限制转化为“下方”这一单一限制 • 可以轻松运用树状数组解决
树状数组简单易懂的详解

树状数组简单易懂的详解1 什么是树状数组树状数组(Fenwick Tree),也叫做树形数组,是一种支持单点修改、区间查询的数据结构。
这种数据结构可以在O(log n)的时间复杂度内实现单点修改和区间查询,相较于线段树,树状数组实现较为简单。
2 树状数组的实现原理树状数组的核心思想是利用二进制中的位运算来维护前缀区间和。
假设有一个数组a[],其前缀和为sum[],那么对于每个下标i,只需要维护[1,i]这个区间的和,即sum[i]。
这样,当需要查询区间[1,r]的和时,只需要计算sum[r]即可,不需要遍历每一个元素。
对于单点修改操作,假设我们要将数组a[]的第i个元素修改为x,我们只需要将第i个元素的值改为x,并且要将[1,i]这个区间内所有的sum数组的值都加上x-a[i],即将差值x-a[i]加到了[1,i]区间中。
3 树状数组的实现细节3.1 树状数组的初始化树状数组需要初始化为0,因为我们需要维护的是每个元素的前缀和,如果没有初始化为0,其前缀和也就变得不可预测了。
查询区间[1,r]的和时,只需要计算sum[r]即可。
具体实现过程为:从r开始向前跳,每一个位置的前缀和都加到一个答案变量ans中,直到跳到0为止。
可以使用以下的代码实现:```C++int query(int x){int ans = 0;while(x > 0){ans += tree[x];x -= lowbit(x);}return ans;}```修改操作也很简单,只需要将第i个位置的值改为x,然后将[1,i]这个区间内所有的sum数组的值都加上x-a[i],即将差值x-a[i]加到了[1,i]区间中,可以使用以下的代码实现:```C++void update(int x, int val){while(x <= n){tree[x] += val;x += lowbit(x);}}```4 树状数组与线段树的比较相对于线段树,树状数组的实现更为简单,而且更加省空间。
树状数组区间求和

树状数组区间求和树状数组是一种用于高效求解区间和的数据结构。
它可以在O(log n)的时间复杂度内完成单点更新和区间求和操作,比传统的数组操作要快得多。
本文将介绍树状数组的原理、实现以及应用场景。
一、树状数组的原理树状数组的原理基于二进制的思想。
假设有一个长度为n的数组a,我们可以将其转化为一个长度为n的树状数组c。
树状数组的每个节点存储了一段原数组的区间和。
树状数组中的每个节点的编号i 代表的是原数组中的第1个到第i个元素的区间和。
树状数组的构建过程如下:1. 初始化树状数组c,将其所有元素置为0。
2. 对于原数组a的每个元素a[i],更新树状数组c的节点c[i],使其加上a[i]。
3. 根据树状数组的性质,每个节点c[i]存储的是原数组a中从i 到i-lowbit(i)+1的区间和。
二、树状数组的实现树状数组的实现需要定义三个关键操作:求和操作、单点更新操作和区间求和操作。
1. 求和操作:从树状数组的根节点开始,不断向左子节点或者右子节点移动,直到达到叶子节点。
叶子节点存储的就是所求区间的和。
2. 单点更新操作:从待更新的节点开始,不断向根节点移动,每次移动时更新当前节点的值。
这样可以保持树状数组的正确性。
3. 区间求和操作:分别求出右边界的区间和和左边界-1的区间和,然后相减,即可得到所求区间的和。
三、树状数组的应用场景树状数组的高效性使得它在很多场景下被广泛应用。
1. 区间求和:树状数组可以高效地求解数组中任意区间的和,比如求解某个区间内的元素和、计算前缀和等。
2. 单点更新:树状数组可以高效地对单个元素进行更新,比如修改某个元素的值或者增加某个元素的数量。
3. 逆序对个数:树状数组可以高效地统计给定数组中逆序对的个数,即后面的元素比前面的元素大的情况。
4. 数组去重:树状数组可以高效地对一个数组进行去重操作,找出数组中的不重复元素。
5. 数组排序:树状数组可以高效地对一个数组进行排序,将数组中的元素按照某个规则重新排列。
用树状数组解决区间查询问题

用树状数组解决区间查询问题分享自斯里猫: 本文扩写自郭神的《树状数组新应用》,在此表示膜拜。
树状数组的学名貌似叫做Binary Index Tree,关于它的基本应用可参考Topcoder上的这篇Tutorial.树状数组可以看作一个受限制的线段树,它维护一个数组,最经典的树状数组支持的基本操作有两个:(1)改变某一个元素的值(2)查询某一个区间内所有元素的和。
在此基础上,经过简单的变形可以变成支持另一组操作:(1)把一个区间内所有元素都加上一个值(2)查询某一个元素的值。
这两个都是已经泛滥了的东西了,在此不赘述。
简单的树状数组模型是不支持这样一组操作的:(1)把某一个区间内所有元素都加上一个值(2)查询某一个区间内所有元素的和。
当然,这个东西可以用线段树完成,但是线段树占内存比较大,写起来也比较繁(对我这种不会数据结构的人而言)。
下面我们用一个改进版的树状数组完成这个任务。
首先一个观察是区间操作总可以变成从最左端开始,比如把区间[3..6]都加10,可以变成[1..6]加10, [1..2]减10。
查询也类似。
于是下面只关心从最左端开始的情况。
定义Insert(p, d)表示把区间[1..p]都加d,Query(p)表示查询区间[1..p]之和。
我们考虑调用一次Insert(p, d)对以后的某次查询Query(q)的影响:(1) 如果p<=q,总的结果会加上p*d (2) 如果p>q,总的结果会加上q*d也就是说,Query(q)的结果来源可分为两部分,一部分是Insert(p1,d) (p1<=q),一部分是Insert(p2,d) (p2 > q)。
我们用两个数组B[], C[]分别维护这两部分信息,B[i]表示区间右端点恰好是i的所有区间的影响之和,C[i]表示区间右端点大于i的所有区间的影响之和。
每当遇到Insert时,考虑当前的Insert会对以后的Query产生什么影响,更新B和C数组;当遇到Query时,把两部分的结果累加起来。
树状数组详解

第01讲什么是树状数组?树状数组用来求区间元素和,求一次区间元素和的时间效率为O(logn)。
有些同学会觉得很奇怪。
用一个数组S[i]保存序列A[]的前i个元素和,那么求区间i,j的元素和不就为S[j]-S[i-1],那么时间效率为O(1),岂不是更快?但是,如果题目的A[]会改变呢?例如:我们来定义下列问题:我们有n个盒子。
可能的操作为1.向盒子k添加石块2.查询从盒子i到盒子j总的石块数自然的解法带有对操作1为O(1)而对操作2为O(n)的时间复杂度。
但是用树状数组,对操作1和2的时间复杂度都为O(logn)。
第02讲图解树状数组C[]现在来说明下树状数组是什么东西?假设序列为A[1]~A[8]网络上面都有这个图,但是我将这个图做了2点改进。
(1)图中有一棵满二叉树,满二叉树的每一个结点对应A[]中的一个元素。
(2)C[i]为A[i]对应的那一列的最高的节点。
现在告诉你:序列C[]就是树状数组。
那么C[]如何求得?C[1]=A[1];C[2]=A[1]+A[2];C[3]=A[3];C[4]=A[1]+A[2]+A[3]+A[4];C[5]=A[5];C[6]=A[5]+A[6];C[7]=A[7];C[8]= A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];以上只是枚举了所有的情况,那么推广到一般情况,得到一个C[i]的抽象定义:因为A[]中的每个元素对应满二叉树的每个叶子,所以我们干脆把A[]中的每个元素当成叶子,那么:C[i]=C[i]的所有叶子的和。
现在不得不引出关于二进制的一个规律:先仔细看下图:将十进制化成二进制,然后观察这些二进制数最右边1的位置:1 --> 000000012 --> 000000103 --> 000000114 --> 000001005 --> 000001016 --> 000001107 --> 000001118 --> 000010001的位置其实从我画的满二叉树中就可以看出来。
数状数组——精选推荐

数状数组⼀、概述树状数组(binary indexed tree),能够⾼效地获取数组中连续n个数的和。
概括说,树状数组通常⽤于解决以下问题:数组A中的元素可能不断地被修改,怎样才能快速地获取连续⼏个数的和?⼆、树状数组基本操作普通数组(共n个元素)的元素修改和连续元素求和的复杂度分别为O(1)和O(n)。
树状数组通过将线性结构数组转换成伪树状结构(线性结构只能逐个扫描元素,⽽树状结构可以实现跳跃式扫描),使得修改和求和复杂度均为O(lgn),⼤⼤提⾼了整体效率。
给定序列(数列)A,我们设⼀个数组C满⾜C[i] = A[i–2^k+ 1] + … + A[i] //其中,k为i在⼆进制下末尾0的个数,i从1开始算,则我们称C为树状数组。
1、给定i,如何求2^k答案是:2^k=i&(-i)例如:当i=6时,C[6]=A[6-2+1]+A[6]当我们修改A[i]的值时,可以从C[i]往根节点⼀路上溯,调整这条路上的所有C[]即可,这个操作的复杂度在最坏情况下就是树的⾼度即O(logn)。
另外,对于求数列的前n项和,只需找到n以前的所有最⼤⼦树,把其根节点的C加起来即可。
不难发现,这些⼦树的数⽬是n在⼆进制时1的个数,或者说是把n展开成2的幂⽅和时的项数,因此,求和操作的复杂度也是O(logn)。
树状数组能快速求任意区间的和:A[i] + A[i+1] + … + A[j],设sum(k) = A[1]+A[2]+…+A[k],则A[i] + A[i+1] + … + A[j] = sum(j)-sum(i-1)。
下⾯是实现代码://求2^kint lowbit(int t){return t & ( t ^ ( t - 1 ) );}//求前n项和int sum(int end){int sum = 0;while(end > 0){sum += C[end];end -= lowbit(end);}return sum;}//增加某个元素的⼤⼩void plus(int pos, int num){ //n是A中元素个数while(pos <= n){C[pos] += num;pos += lowbit(pos);}}3、扩展——⼆维树状数组⼀维树状数组很容易扩展到⼆维,⼆维树状数组如下所⽰:C[x][y] = sum(A[i][j])其中,x-lowbit[x]+1 <= i<=x且y-lowbit[y]+1 <= j <=y4、应⽤(1)⼀维树状数组:(2)⼆维树状数组:⼀个由数字构成的⼤矩阵,能进⾏两种操作1) 对矩阵⾥的某个数加上⼀个整数(可正可负)2) 查询某个⼦矩阵⾥所有数字的和,要求对每次查询,输出结果。
树状数组

要想知道c[i]表示的是哪个区域的和,只要 求出2^k(lowbit); 树状数组之所以高效简洁的原因就是能 够利用位运算直接求出i对应的lowbit
int lowbit(int i) { return i & ( -i ); } 解释: 假设i为 则-i的 :1) 取反 2)+1
2^K(lowbit)求法
Thanks... ...
求出各个级别的星星的个数
题目分析:
算法有很多种,最实用的是树状数 组 由于本题的数据输入是以y坐标 的升序输入,所以,只需要比较 x坐标即可 树状数组存储的是某一段范围内 有多少个点,即求和.
小结
在很多的情况下,线段树都可以用树状 数组实现.凡是能用树状数组的一定能 用线段树.当题目不满足减法原则的时 候,就只能用线段树,不能用树状数组.例 如数列操作如果让我们求出一段数字中 最大或者最小的数字,就不能用树状数 组了. 树状数组的每个操作都是0(log(n))的复 杂度.
怎 么 办
用树状数组!!!
下图中的C数组就是树状数组,a 数组是原数组
先自己研究一下这个东西
可以发现这些规律 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 …… C2^n=a1+a2+….+a2^n
树状数组
先看一个例题: 数列操作: 给定一个初始值都为0的序列,动 态地修改一些位置上的数字,加 上一个数,减去一个数, 然后动态 地提出问题,问题的形式是求出 一段数字的和.
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
树状数组及其应用( Binary Indexed Trees )一、什么是树状数组【引例】假设有一列数{A i}(1<=i<=n),支持如下两种操作:1.将A k的值加D。
(k,D是输入的数)2.输出A s+A s+1+…+A t(s,t都是输入的数,s<=t)分析一:线段树建立一颗线段树(线段长度1~n)。
一开始所有结点的count值等于0。
对于操作1,如果把A k的值加D,则把所有覆盖了A k的线段的count值加D。
只有log2n 条线段会受到影响,因此时间复杂度是O(log2n)。
每条线段[x..y]的count值实际上就是A x+A x+1+…+A y的值。
对于操作2,实际上就是把[s..t]这条线段分解成为线段树中的结点线段,然后把所有的结点线段的count值相加。
该操作(ADD操作)在上一讲线段树中已介绍。
时间复杂度为O (log2n)。
分析二:树状数组树状数组是一种特殊的数据结构,这种数据结构的时空复杂度和线段树相似,但是它的系数要小得多。
增加数组C,其中C[i]=a[i-2^k+1]+……+a[i](k为i在二进制形式下末尾0的个数)。
由c数组的定义可以得出:为了对树状数组有个形象的认识,我们先看下面这张图。
如图所示,红色矩形表示的数组C[]就是树状数组。
我们也不难发现,这个k就是该节点在树中的高度,因而这个树的高度不会超过logn。
【操作1】修改A[i]的值。
可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,这个操作的复杂度在最坏情况下就是树的高度即O(logn)。
定理1:若a[k]所牵动的序列为C[p1],C[p2]……C[p m],则p1=k,而p i+1=p i+2li (l i为p i在二进制中末尾0的个数)。
例如a[1]……a[8]中,a[3] 添加x;p1=k=3 p2=3+20=4p3=4+22=8 p4=8+23=16>8由此得出,c[3]、c[4]、c[8]亦应该添加x。
定理的证明如下:【引理】若a[k]所牵动的序列为C[p1],C[p2] ……C[p m],且p1 <p2 < ……<p m,则有l1 <l2< ……<l m(l i为p i在二进制中末尾0的个数)。
证明:若存在某个i有l i≥l i+1,则p i-2 li +1≤k≤p i,p i+1-2li+1+1≤k≤p i+1,p i+1– 2 Li+1+1≤k≤p i,即:P i+1≤P i+2Li+1–1 (1)而由L i=L i+1、P i < P i+1可得P i+1≥P i + 2Li(2)(1) (2)矛盾,可知l1 <l2 <……<l m定理:p1=k,而p i+1=p i+2li证明:因为p1 <p2 < ……<p m且C[p1],C[p2] ……C[p m]中包含a[k],因此p1=k。
在p 序列中,p i+1=p i +2li是p i后最小的一个满足l i+1 >li的数(若出现P i+x比p i+1更小,则x<2li,与x在二进制中的位数小于l i相矛盾)。
P i+1=p i+2li,l i+1≥l i+1。
由p i-2li+1≤K≤P i可知,P i+1-2li+1+1≤P i+2li–2*2li+1=P i– 2li+1≤K≤P i≤P i+1 ,故P i与p i+1之间的递推关系式为P i+1=P i+2li【操作2】求数列的前n项和。
只需找到n以前的所有最大子树,把其根节点的C加起来即可。
不难发现,这些子树的数目是n在二进制时1的个数,或者说是把n展开成2的幂方和时的项数, 因此,求和操作的复杂度也是O(logn)。
根据c[k]=a[k-2l+1]+ … +a[k] (l为k在二进制数中末尾0的个数),我们从k1=k出发,按照k i+1=k i-2lki(lk i为k i在二进制数中末尾0的个数)递推k2,k3,…,k m (k m+1=0)。
由此得出S=c[k1]+c[k2]+c[k3] + … + c[k m]例如,计算a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]k1=7k2= k1-2l1=7-20=6k3= k2-2l2=6-21=4k4= k3-2l3=4-22=0即a[1]+a[2]+ a[3]+a[4]+ a[5]+a[6]+ a[7]=c[7]+c[6]+c[4]二、树状数组的操作函数在操作1和操作2中,我们反复提到求2^K(k为i的2进制中末尾0的个数),因此我们先来定义一个求数i的低位函数,返回这个值。
求低位函数(LowBit)LowBit,即2进制数中从最低位开始连续0的位数的关于2的幂,其值LowBit(x)= x and -x。
LowBit(x)显然就是not x中最低的是0的那一位,(not x)+1的那一位则会变成1,其更低的位全部变成0,而更高的位不变。
由于更高的位就是原数取反,和原数求and的值为0,最低位就是唯一的是1的位了。
所以LowBit(x)=x and((not x)+1)。
举例说明:在x=10101000时,x=10101000not x=01010111(not x)+1=01011000和原数求and就是1000。
同时not x=-x-1,所以LowBit(x)=x and -x。
有了lowbit函数,我们就可以方便地实现树状数组的修改(modify)、求和(getsum)两个操作。
操作1:modify(i, num)modify(i, num):对数组a[] 中的第i 个元素加上num。
为了维护c[] 数组,我就必须要把c[] 中所有“管”着a[i] 的c[i] 全部加上num,这样才能随时以O(logn) 的复杂度进行getsum(i) 的操作。
而"i:=i+ lowbit(i)" 正是依次访问所有包含a[i] 的c[i] 的过程。
修改a[i],我们需对c[i] , c[i+lowbit(i)] , c[i+lowbit(i)+lowbit(i+lowbit(i))] ……进行修改。
复杂度O(logn) 。
pascal代码:procedure modify(x,delta:longint);beginwhile x<=n dobegininc(c[x],delta);inc(x,lowbit(x));end;end;操作2:求和(getsum)getsum(i): 求和正好反过来,每次“i:=i-l owbit(i)” 依次求a[i] 之前的某一段和。
因为c[i] 有这样一个性质:Lowbit(i) 的值即为c[i] “管”着a[i] 中元素的个数,比如i = (101100)2,那么c[i] 就是从a[i] 开始往前数(100) 2 = 4 个元素的和,也就是c[i] = a[i] + a[i - 1] + a[i - 2] + a[i - 3]。
那么每次减去lowbit(i) 就是依次跳过当前c[i] 所能管辖的范围,以便不重不漏地求出所有a[i] 之前的元素之和。
a[1]+...+a[i]=c[i]+c[i-lowbit(i)]+c[i-lowbit(i)-lowbit(i-lowbit(i))]……复杂度O(logn)pascal代码:function sum(x:longint):longint;beginsum:=0;while x>0dobegininc(sum,c[x]);dec(x,lowbit(x));end;end;三、树状数组的应用【例1】Stars(POJ2352)(时间1秒,空间64M)/JudgeOnline/problem?id=2352DescriptionAstronomers often examine star maps where stars are represented by points on a plane and each star has Cartesian coordinates. Let the level of a star be an amount of the stars that are not higher and not to the right of the given star. Astronomers want to know the distribution of the levels of the stars.For example, look at the map shown on the figure above. Level of the star number 5 is equal to 3 (it's formed by three stars with a numbers 1, 2 and 4). And the levels of the stars numbered by 2 and 4 are 1. At this map there are only one star of the level 0, two stars of the level 1, one star of the level 2, and one star of the level 3.You are to write a program that will count the amounts of the stars of each level on a given map.InputThe first line of the input file contains a number of stars N (1<=N<=15000). The following N lines describe coordinates of stars (two integers X and Y per line separated by a space, 0<=X,Y<=32000). There can be only one star at one point of the plane. Stars are listed in ascending order of Y coordinate. Stars with equal Y coordinates are listed in ascending order of X coordinate.OutputThe output should contain N lines, one number per line. The first line contains amount of stars of the level 0, the second does amount of stars of the level 1 and so on, the last line contains amount of stars of the level N-1.Sample Input51 15 17 13 35 5Sample Output1211HintThis problem has huge input data,use scanf() instead of cin to read data to avoid time limit exceed.【题目大意】有N(1<=N<=15000)颗星星,坐标(0<=X,Y<=32000)。