树状数组的原英文表达:Binary Indexed Tree(BIT),直译的意思便是:二进制标记树
如果数组A是基础数组,数组C是区间数组。那么,在具体介绍数组C的特点前,先给出如下的树状关系图:
仔细观察上图,容易发现:
数组C[]分别代表的区间为:
C1=A1 [1,1]
C2=C1+A2=A1+A2 [1,2]
C3=A3 [3,3]
C4=C2+C3+A4=A1+A2+A3+A4 [1,4]
C5=A5 [5,5]
C6=C5+A6=A5+A6 [5,6]
C7=A7 [7,7]
C8=C4+C6+C7+A8=A1+A2+A3+A4+A5+A6+A7+A8 [1,8]
C9=A9 [9,9]
也就是说,每个数组Ci,至少包含Ai,同时包含所有满足j+lowest_bit(j)=i的Cj数组。例如C8不仅包含A8,同时还包含了C4,C6,C7。而
410=1002,1002+1002=10002=810
610=1102,1102+102=10002=810
710=1112,1112+12=10002=810
注:lowest_bit(i)表示计算数字i的二进制表示中,从右往左数,第一个1所代表的数字。
利用位运算,
我们容易得到lowest_bit()的快速计算方法:
int lowbit(int x)
{
return x&-x;
}
于是,在Ai更新时,只需纵向分别更新即可:C[i],C[i=i+lowest_bit(i)]……直到i>n。
例如在更新A1时候,我们分别更新C1,C2,C4与C8。(这里假设n=9)
对于更新过程我们可以这样理解:更新所有包含Ai的数组Cj。计算下标j的过程类似于在树形结构中寻找父节点的过程。
实现代码:
voidadd(int
x,int
val)
{
while(x<=n)
{
c[x]+=val;
x+=lowbit(x);
}
}
对于每个数组Ci,至少包含Ai,同时包含所有满足j+lowest_bit(j)=i的Cj数组。因此,可以得到如下结论:数组Ci代表的区间一定是:[i-lowest_bit(i)+1,i]。(这里略去了该结论的证明过程)
于是,对于A1+A2+……Ai的和,我们只需找到一组能完美覆盖区间[1,i]的数组集合{C[]}即可:C[i]+C[i=i-lowest_bit(i)]+……直到i=0。
例如在查询A1+A2+……A7的值时,我们累加C7+C6+C4
实现代码:
intsum(int x)
{
int rt=0;
while(x)
{
rt+=c[x];
x-=lowbit(x);
}
return rt;
}
可以看出,树状数组的代码实现非常简洁,极易编码。同时,我们容易计算出树状数组的更新操作的时间复杂度为log(n),查询操作的时间复杂度同样为log(n),因此总时间复杂度为log(n)。
[区间更新单一查询]
若需要区间更新,单一查询,那么只要改变数组C的含义即可:数组C表示区间的共同增量,例如,
C1=A1
[1,1]
C2=C1+A2=A1+A2
[1,2]
C3=A3
[3,3]
C4=C2+C3+A4=A1+A2+A3+A4
[1,4]
分别表示区间[1,1],[1,2],[3,3],[1,4]的共同增量,于是在更新区间[1,i]时,我们只需找到一组能完美覆盖区间[1,i]的数组集合{C[]}即可,这刚好对应着之前树状数组的sum操作。于是,我们将sum操作更改为add操作,即
voidadd(int
x,
intval)
{
while(x)
{
c[x]+=val;
x-=lowbit(x);
}
}
对于区间[s,t],我们只需执行add(t,val)与add(s-1,-val)即可。
对于单一查询:query(i),我们只需累加所有包含Ai的数组Cj即可,这对应着之前树状数组的add操作。于是,我们将add操作更改为sum操作,即
intsum(int
x)
{
int
rt=0;
while(x<=n)
{
rt+=c[x];
x+=lowbit(x);
}
return
rt;
}
[区间更新区间查询]
更一般的,有时候题目同时要求我们区间更新与区间查询,例如:
Description
You
have N integers, A1, A2,
... , AN.
You need to deal with two kinds of operations. One type of operation
is to add some given number to each number in a given interval. The
other is to ask for the sum of numbers in a given interval.
Input
The
first line contains two numbers N and Q.
1 ≤ N,Q ≤
100000.
The second line contains N numbers,
the initial values of A1, A2,
... , AN.
-1000000000 ≤ Ai ≤
1000000000.
Each of the next Q lines
represents an operation.
"C a b c"
means adding c to
each of Aa, Aa+1,
... , Ab.
-10000 ≤ c ≤
10000.
"Q a b"
means querying the sum of Aa, Aa+1,
... , Ab.
Output
You
need to answer all Q commands
in order. One answer in a line.
Sample
Input
10
5
1
2 3 4 5 6 7 8 9 10
Q
4 4
Q
1 10
Q
2 4
C
3 6 3
Q
2 4
Sample
Output
4
55
9
15
Hint
The
sums may exceed the range of 32-bit integers.
Source
POJ
Monthly--2007.11.25,
Yang Yi
这个题目求的是某一区间的数组和,而且要支持批量更新某一区间内元素的值,怎么办呢?实际上,还是可以把该问题转化为求数组的前缀和。
首先,看更新操作update(s,
t, d)把区间A[s]……A[t]都增加d,我们引入一个数组delta[i],表示
A[i]……A[n]的共同增量,n是数组的大小。那么update操作可以转化为:
1)令delta[s]
= delta[s] + d,表示将A[s]……A[n]同时增加d,但这样A[t+1]……A[n]就多加了d,所以
2)再令delta[t+1]
= delta[t+1] - d,表示将A[t+1]……A[n]同时减d
再看查询操作query(s,
t),求A[s]……A[t]的区间和,转化为求前缀和,设sum[i]
= A[1]+ ……+A[i],
则A[s]+
……+A[t]
= sum[t] - sum[s-1],
那么前缀和sum[x]又如何求呢?它由两部分组成,一是数组的原始和,二是该区间内的累计增量和,把数组A的原始值保存在数组org中,并且delta[i]对sum[x]的贡献值为delta[i]*(x+1-i),
那么
sum[x]
= org[1]+……+org[x] +
delta[1]*x + delta[2]*(x-1) +……+delta[x]*1
=
org[1]+……+org[x] +
segma(delta[i]*(x+1-i))
=
segma(org[i]) + (x+1)*segma(delta[i])
-
segma(delta[i]*i),1
<= i <= x
//
segma,即求和
这其实就是三个数组org[i],
delta[i]和delta[i]*i的前缀和,org[i]的前缀和保持不变,事先就可以求出来,delta[i]和delta[i]*i的前缀和是不断变化的,并且每次都是单一更新,所以可以用两个树状数组来维护。
对于delta[i]*i,其实就delta[i]的简单映射关系,于是update操作可以转化为:
1)令delta[s]*s
= (delta[s]
+ d)*s
= delta[s]*s+s*d,
2)再令delta[t+1]*(t+1)
= delta[t+1]*(t+1)
– (t+1)*d,
于是,我们有了如下的代码实现:
scanf("%s",opt);
if(opt[0]==‘Q‘)
{
LL
res;
scanf("%d%d",&a,&b);
res=org[b]+(b+1)*sum(b,0)-sum(b,1);
res-=(org[a-1]+a*sum(a-1,0)-sum(a-1,1));
printf("%I64d\n",res);
}
else
{
scanf("%d%d%d",&a,&b,&c);
add(a,c,0),add(b+1,-c,0);
add(a,c*a,1),add(b+1,-c*(b+1),1);
}
细数BIT各种用法,不得不感叹这种简单数据结构的强大与灵活!
趁热打铁,几道简单BIT:
hdu
1394 Minimum Inversion Number
BIT的巧妙运用:
BIT解决区间第k大元素:
pku
2886 Who Gets the Most Candies?
BIT的区间维护与区间查询:
pku
3468 A Simple Problem with Integers
转载自:YB大神