树状数组(BIT)

树状数组的原英文表达:Binary Indexed TreeBIT,直译的意思便是:二进制标记树

 

如果数组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]

 

每个 c[i] 管理的区间是   [i - bitlow(i) + 1, i] 

 

也就是说,每个数组Ci,至少包含Ai,同时包含所有满足j+lowest_bit(j)=iCj数组。例如C8不仅包含A8,同时还包含了C4,C6,C7。而

 

410=10021002+1002=10002=810

 

610=11021102+102=10002=810

 

710=11121112+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,C4C8。(这里假设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)=iCj数组。因此,可以得到如下结论:数组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 1166 敌兵布阵

pku 2299 Ultra_QuickSort

hdu 1394 Minimum Inversion Number

pku 1195 Mobile phones

hdu 1892 See you~

pku 2352 Stars

hdu 2492 Ping pong

pku 1990 MooFest

pku 3067 Japan


BIT的巧妙运用:

pku 2155 Matrix

hdu 3584 Cube

pku 3321 Apple Tree

pku 3378 Crazy Thairs


BIT解决区间第k大元素:

pku 2761 Feed the dogs

pku 2886 Who Gets the Most Candies?


BIT的区间维护与区间查询:

pku 3468 A Simple Problem with Integers

转载自:YB大神

 

 

 

转载于:https://www.cnblogs.com/tenlee/p/4775231.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值