在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]项的元素更新即可
方式二