开始BB
树状数组,类似于线段树,只不过处理问题范围相对小一些(???)
不过,优点在于时间,空间和代码复杂度都小得多。
对比一下(a是原数组):
线段树:
树状数组:
设tree为c
根据图可以知道,c[i]=a[i-2^k+1]+…+a[i] (k为i二进制下结尾0的个数)
设i=10=1010 (2)
因为结尾有1个0,所以10的父节点就是有2个0的节点。
即12 =1100(2)
很显然可以知道,只需要把10+2 (二进制下为10),就可以得到12。
2即等于2^1。
所以一个节点的父节点即为i+2^k。
比如我要求从1~10的节点和,要怎样用树状数组计算?
根据上面可以得知,树中一个节点的控制范围是在i-2^k+1~i。
那么把i减去2^k,可以得到?~i-2^k的范围 (?为新的i的k)
可以发现,这两个区间没有任何重合,中间也没有任何空缺。
所以可以直接把区间的和加上。
所以一个区间的下一个区间的结尾是i-2^k。
还有,如何求k?
还是以10为例:
10=1010 (2)
科普一下:
一个数的相反数的二进制等于它的补码+1
补码:即0变1,1变0
所以
+10=0000…01010
-10=1111…10110
是不是发现了什么???
如果一个数为00….00100…000,
那么反过来为11….11011…111
再加上一,为11….11100…000
后k+1为是相同的!
而且第k+1位一定为1,后面的都为0
所以很显然,K=i and (-i)
自己理解
所以上面给出的两道题目,解法就不细讲了
都是用树状数组搞一搞,然后算答案就行了
1978(模板题):
var
d:array[1..100000] of longint;
t:array[1..100000] of longint;
power:array[1..100000] of longint;
n,m,i,j,k,l,x,y:longint;
ch:char;
procedure change(x,s:longint);
begin
while x<=n do
begin
t[x]:=t[x]+s;
x:=x+power[x];
end;
end;
begin
readln(n);
for i:=1 to n do
begin
readln(t[i]);
d[i]:=t[i];
end;
for i:=1 to n do
power[i]:=i and (-i);
for i:=1 to n-1 do
if i+power[i]<=n then
t[i+power[i]]:=t[i+power[i]]+t[i];
readln(m);
for i:=1 to m do
begin
read(ch);
readln(j,k);
if ch='Q' then
begin
x:=0;
y:=0;
l:=j-1;
while l>=1 do
begin
x:=x+t[l];
l:=l-power[l];
end;
l:=k;
while l>=1 do
begin
y:=y+t[l];
l:=l-power[l];
end;
writeln(y-x);
end
else
begin
change(j,k-d[j]);
d[j]:=k;
end;
end;
end.
1979:
var
t:array[1..32001] of longint;
p:array[1..32001] of longint;
n,i,j,k,x,y:longint;
begin
readln(n);
for i:=1 to 32001 do
p[i]:=i and (-i);
for i:=1 to n do
begin
readln(x,y);
inc(x);
j:=x;
k:=0;
while x>=1 do
begin
k:=k+t[x];
x:=x-p[x];
end;
writeln(k);
while j<=32001 do
begin
inc(t[j]);
j:=j+p[j];
end;
end;
end.