树状数组及其应用.docx

上传人:b****4 文档编号:27166598 上传时间:2023-06-27 格式:DOCX 页数:16 大小:136.15KB
下载 相关 举报
树状数组及其应用.docx_第1页
第1页 / 共16页
树状数组及其应用.docx_第2页
第2页 / 共16页
树状数组及其应用.docx_第3页
第3页 / 共16页
树状数组及其应用.docx_第4页
第4页 / 共16页
树状数组及其应用.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

树状数组及其应用.docx

《树状数组及其应用.docx》由会员分享,可在线阅读,更多相关《树状数组及其应用.docx(16页珍藏版)》请在冰豆网上搜索。

树状数组及其应用.docx

树状数组及其应用

树状数组及其应用

(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]项的元素更新即可

方式二

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 初中教育 > 科学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1