树状数组及其应用.docx
- 文档编号:27166598
- 上传时间:2023-06-27
- 格式:DOCX
- 页数:16
- 大小:136.15KB
树状数组及其应用.docx
《树状数组及其应用.docx》由会员分享,可在线阅读,更多相关《树状数组及其应用.docx(16页珍藏版)》请在冰豆网上搜索。
树状数组及其应用
树状数组及其应用
(BinaryIndexedTrees)
一、什么是树状数组
【引例】
假设有一列数{Ai}(1<=i<=n),支持如下两种操作:
1.将Ak的值加D。
(k,D是输入的数)
2.输出As+As+1+…+At(s,t都是输入的数,s<=t)
分析一:
线段树
建立一颗线段树(线段长度1~n)。
一开始所有结点的count值等于0。
对于操作1,如果把Ak的值加D,则把所有覆盖了Ak的线段的count值加D。
只有log2n条线段会受到影响,因此时间复杂度是O(log2n)。
每条线段[x..y]的count值实际上就是Ax+Ax+1+…+Ay的值。
对于操作2,实际上就是把[s..t]这条线段分解成为线段树中的结点线段,然后把所有的结点线段的count值相加。
该操作(ADD操作)在上一讲线段树中已介绍。
时间复杂度为O(log2n)。
分析二:
树状数组
树状数组是一种特殊的数据结构,这种数据结构的时空复杂度和线段树相似,但是它的系数要小得多。
增加数组C,其中C[i]=a[i-2^k+1]+……+a[i](k为i在二进制形式下末尾0的个数)。
由c数组的定义可以得出:
i
K
1
(1)2
0
1-2^0+1=1…1
c[1]=a[1]
2
(10)2
1
2-2^1+1=1…2
c[2]=a[1]+a[2]=c[1]+a[2]
3
(11)2
0
3-2^0+1=3…3
c[3]=a[3]
4
(100)2
2
4-2^2+1=1…4
c[4]=a[1]+a[2]+a[3]+a[4]=c[2]+c[3]+a[4]
5
(101)2
0
5-2^0+1=5…5
c[5]=a[5]
6
(110)2
1
6-2^1+1=5…6
c[6]=a[5]+a[6]=c[5]+a[6]
………………
为了对树状数组有个形象的认识,我们先看下面这张图。
如图所示,红色矩形表示的数组C[]就是树状数组。
我们也不难发现,这个k就是该节点在树中的高度,因而这个树的高度不会超过logn。
【操作1】修改A[i]的值。
可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,这个操作的复杂度在最坏情况下就是树的高度即O(logn)。
定理1:
若a[k]所牵动的序列为C[p1],C[p2]……C[pm],则p1=k,而pi+1=pi+2li(li为pi在二进制中末尾0的个数)。
例如a[1]……a[8]中,a[3]添加x;
p1=k=3p2=3+20=4
p3=4+22=8p4=8+23=16>8
由此得出,c[3]、c[4]、c[8]亦应该添加x。
定理的证明如下:
【引理】
若a[k]所牵动的序列为C[p1],C[p2]……C[pm],且p1 证明: 若存在某个i有li≥li+1,则pi-2li+1≤k≤pi,pi+1-2li+1+1≤k≤pi+1,pi+1–2Li+1+1≤k≤pi,即: Pi+1≤Pi+2Li+1–1 (1) 而由Li=Li+1、Pi Pi+1≥Pi+2Li (2) (1) (2)矛盾,可知l1 定理: p1=k,而pi+1=pi+2li 证明: 因为p1 在p序列中,pi+1=pi+2li是pi后最小的一个满足li+1>li的数(若出现Pi+x比pi+1更小,则x<2li,与x在二进制中的位数小于li相矛盾)。 Pi+1=pi+2li,li+1≥li+1。 由pi-2li+1≤K≤Pi可知,Pi+1-2li+1+1≤Pi+2li–2*2li+1=Pi–2li+1≤K≤Pi≤Pi+1,故Pi与pi+1之间的递推关系式为 Pi+1=Pi+2li 【操作2】求数列的前n项和。 只需找到n以前的所有最大子树,把其根节点的C加起来即可。 不难发现,这些子树的数目是n在二进制时1的个数,或者说是把n展开成2的幂方和时的项数, 因此,求和操作的复杂度也是O(logn)。 根据c[k]=a[k-2l+1]+…+a[k](l为k在二进制数中末尾0的个数),我们从k1=k出发,按照 ki+1=ki-2lki(lki为ki在二进制数中末尾0的个数) 递推k2,k3,…,km(km+1=0)。 由此得出 S=c[k1]+c[k2]+c[k3]+…+c[km] 例如,计算a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7] k1=7 k2=k1-2l1=7-20=6 k3=k2-2l2=6-21=4 k4=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)=xand-x。 LowBit(x)显然就是notx中最低的是0的那一位,(notx)+1的那一位则会变成1,其更低的位全部变成0,而更高的位不变。 由于更高的位就是原数取反,和原数求and的值为0,最低位就是唯一的是1的位了。 所以LowBit(x)=xand((notx)+1)。 举例说明: 在x=10101000时, x=10101000 notx=01010111 (notx)+1=01011000 和原数求and就是1000。 同时notx=-x-1,所以LowBit(x)=xand-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代码: proceduremodify(x,delta: longint); begin whilex<=ndo begin inc(c[x],delta); inc(x,lowbit(x)); end; end; 操作2: 求和(getsum) getsum(i): 求和正好反过来,每次“i: =i-lowbit(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代码: functionsum(x: longint): longint; begin sum: =0; whilex>0do begin inc(sum,c[x]); dec(x,lowbit(x)); end; end; 三、树状数组的应用 【例1】Stars(POJ2352)(时间1秒,空间64M) Description AstronomersoftenexaminestarmapswherestarsarerepresentedbypointsonaplaneandeachstarhasCartesiancoordinates.Letthelevelofastarbeanamountofthestarsthatarenothigherandnottotherightofthegivenstar.Astronomerswanttoknowthedistributionofthelevelsofthestars. Forexample,lookatthemapshownonthefigureabove.Levelofthestarnumber5isequalto3(it'sformedbythreestarswithanumbers1,2and4).Andthelevelsofthestarsnumberedby2and4are1.Atthismapthereareonlyonestarofthelevel0,twostarsofthelevel1,onestarofthelevel2,andonestarofthelevel3. Youaretowriteaprogramthatwillcounttheamountsofthestarsofeachlevelonagivenmap. Input ThefirstlineoftheinputfilecontainsanumberofstarsN(1<=N<=15000).ThefollowingNlinesdescribecoordinatesofstars(twointegersXandYperlineseparatedbyaspace,0<=X,Y<=32000).Therecanbeonlyonestaratonepointoftheplane.StarsarelistedinascendingorderofYcoordinate.StarswithequalYcoordinatesarelistedinascendingorderofXcoordinate. Output TheoutputshouldcontainNlines,onenumberperline.Thefirstlinecontainsamountofstarsofthelevel0,theseconddoesamountofstarsofthelevel1andsoon,thelastlinecontainsamountofstarsofthelevelN-1. SampleInput 5 11 51 71 33 55 SampleOutput 1 2 1 1 0 Hint Thisproblemhashugeinputdata,usescanf()insteadofcintoreaddatatoavoidtimelimitexceed. 【题目大意】 有N(1<=N<=15000)颗星星,坐标(0<=X,Y<=32000)。 每颗星的级别定义为: 不比它高、也不比它靠右的星星个数(即左下角,包含边界)。 给出每颗星的坐标。 输入保证Y坐标升序,Y相等时X坐标升序。 求每个级别的星星数。 【分析】 由于题目的输入已经是按照Y的升序,(如果Y相同,就按照X升序排列),因此可以做到每录入一个数据,就计算出它的level同时update,用c为每个坐标为X的数据做记录(利用树状数组组织)。 Level统计的时候,利用树状数组,在o(logn)时间内完成。 【参考代码】 programpoj2352; var n: longint; c: array[0..32005]oflongint; ans: array[0..15001]oflongint; functionlowbit(x: longint): longint; begin lowbit: =xand(-x); end; procedurechange(k: longint); begin whilek<=32001do begin c[k]: =c[k]+1; k: =k+lowbit(k); end; end; functiongetsum(k: longint): longint; var tot: longint; begin tot: =0; whilek>0do begin tot: =tot+c[k]; k: =k-lowbit(k); end; getsum: =tot; end; procedurework; var i,x,y: longint; begin fillchar(c,sizeof(c),0); fillchar(ans,sizeof(ans),0); readln(n); fori: =1tondo begin readln(x,y); inc(ans[getsum(x+1)]); change(x+1); end; fori: =0ton-1do writeln(ans[i]); end; begin work; end. 【例2】假设有一列数{Ai}(1<=i<=n),支持如下两种操作: 1.将A[x]…A[y]的值加D。 (x,y,D都是输入的数); 2.输出Ak的值。 【分析】 我们把支持这种操作的树状数组称为树状数组的模式二,对于模式二,树状数组可以做到随时修改数组a[]中某个区间的值(O (1)),查询某个元素的值(O(logn)) 在这种模式下,a[i]已经不再表示真实的值了,只不过是一个没有意义的、用来辅助的数组。 这时我们真正需要的是另一个假想的数组b[],b[i]才表示真实的元素值。 但c[]数组却始终是为a[]数组服务的,这一点大家要明确。 此时getsum(i)虽然也是求a[i]之前的元素和,但它现在表示的是实际我要的值,也就是b[i]。 比如现在我要对图1中a[]数组中红色区域的值全部加1。 当然你可以用模式一的modify(i)对该区间内的每一个元素都修改一次,但如果这个区间很大,那么每次修改的复杂度就都是O(nlogn),m次修改就是O(mnlogn),这在m和n很大的时候仍是不满足要求的。 这时模式二便派上了用场。 我只要将该区域的第一个元素+1,最后一个元素的下一位置-1,对每个位置getsum(i)以后的值见图2: 相信大家已经看得很清楚了,数组b[]正是我们想要的结果。 模式二难理解主要在于a[]数组的意义。 这时请不要再管a[i]表示什么,a[i]已经没有意义了,我们需要的是b[i]! 但模式二同样存在一个缺陷,如果要对某个区间内的元素求和,复杂度就变成O(nlogn)了。 所以要分清两种模式的优缺点,根据题目的条件选择合适的模式,灵活应变! 顺便给出二维树状数组模式二的修改方法: 【例3】mobilephone(移动电话) Description SupposethatthefourthgenerationmobilephonebasestationsintheTampereareaoperateasfollows.Theareaisdividedintosquares.ThesquaresformanS*Smatrixwiththerowsandcolumnsnumberedfrom0toS-1.Eachsquarecontainsabasestation.Thenumberofactivemobilephonesinsideasquarecanchangebecauseaphoneismovedfromasquaretoanotheroraphoneisswitchedonoroff.Attimes,eachbasestationreportsthechangeinthenumberofactivephonestothemainbasestationalongwiththerowandthecolumnofthematrix. Writeaprogram,whichreceivesthesereportsandanswersqueriesaboutthecurrenttotalnumberofactivemobilephonesinanyrectangle-shapedarea. Input Theinputisreadfromstandardinputasintegersandtheanswerstothequeriesarewrittentostandardoutputasintegers.Theinputisencodedasfollows.Eachinputcomesonaseparateline,andconsistsofoneinstructionintegerandanumberofparameterintegersaccordingtothefollowingtable. Thevalueswillalwaysbeinrange,sothereisnoneedtocheckthem.Inparticular,ifAisnegative,itcanbeassumedthatitwillnotreducethesquarevaluebelowzero.Theindexingstartsat0,e.g.foratableofsize4*4,wehave0<=X<=3and0<=Y<=3. Tablesize: 1*1<=S*S<=1024*1024 CellvalueVatanytime: 0<=V<=32767 Updateamount: -32768<=A<=32767 Noofinstructionsininput: 3<=U<=60002 Maximumnumberofphonesinthewholetable: M=2^30 Output Yourprogramshouldnotansweranythingtolineswithaninstructionotherthan2.Iftheinstructionis2,thenyourprogramisexpectedtoanswerthequerybywritingtheanswerasasinglelinecontainingasingleintegertostandardoutput. SampleInput 04 1123 20022 1112 112-1 21123 3 SampleOutput 3 4 【问题描述】 假设第四代移动电话的收发站是这样运行。 整个区域被分割成很小的方格。 所有的方格组成了一个S*S的矩阵,行和列从0~S-1编号。 每个小方格都包含一个收发站。 每个方格内的手机的移动电话数量可以不断改变,因为手机用户在各个方格之间移动,也有用户开机或者关机。 一旦某个方格里面开机的移动电话数量发生了变化,该方格里的收发站就会向总部发送一条信息说明这个改变量。 总部要你写一个程序,用来管理从各个收发站收到的信息。 老板可能随时会问: 某个给定矩形区域内有多少部开机的移动电话啊? 你的程序必须要能随时回答老板的问题。 【输入输出数据】 从标准输入读入整数,向标准输出写入你对老板的回答。 输入数据的格式如下: 每个输入独立成一行。 一个输入包括一个指示数和一些参数,见下表: 指示数 参数 意义 0 S 初始指令。 整个区域由S*S个小方格组成。 这个指令只会在一开始出现一次。 1 XYA 方格(X,Y)内的开机移动电话量增加了A。 A可能是正数也可能是负数 2 LBRT 询问在矩形区域(L,B)—(R,T)内有多少部开机的移动电话。 矩形区域(L,B)—(R,T)包括所有的格子(X,Y)满足L<=X<=R,B<=Y<=T. 3 终止程序。 这个指令只会在最后出现一次。 所有的数据总是在给定范围内,你不需要差错。 特别的,如果A是负数,你可以认为该操作不会让该格子的开机移动电话数变成负数。 格子是从0开始编号的,比如一个4*4的区域,所有的格子(X,Y)应该表示为: 0<=X<=3,0<=Y<=3。 对于除了2之外的指示,你的程序不应该输出任何东西。 如果指示是2,那么你的程序应该向标准输出写入一个整数。 【数据限制】 区域大小 S*S 1*1<=S*S<=1024*1024 每个格子的值 V 0<=V<=32767 增加/减少量 A -32768<=A<=32767 指令总数 U 3<=U<=60002 所有格子的和 M M=2^30 【分析】 由于题目基本上只是在做查询和插入,因此我们可以完全不需要一个基本数组,而直接使用树状数组的就可以完成所有的数据的记录 方式一就是二维树状数组的add,只要把对应包含[x,y]项的元素更新即可 方式二
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 树状 数组 及其 应用