树状数组(BIT)

树状数组的原英文表达: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, A1A2,
... , 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 A1A2,
... , AN.
-1000000000 ≤ Ai ≤
1000000000.
Each of the next Q lines
represents an operation.
"C a b c"
means adding c to
each of AaAa+1,
... , Ab.
-10000 ≤ c ≤
10000.
"Q a b"
means querying the sum of AaAa+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大神

时间: 2024-11-06 09:51:49

树状数组(BIT)的相关文章

HDU 5542 The Battle of Chibi dp+树状数组

题目:http://acm.hdu.edu.cn/showproblem.php?pid=5542 题意:给你n个数,求其中上升子序列长度为m的个数 可以考虑用dp[i][j]表示以a[i]结尾的长度为j的上升子序列有多少 裸的dp是o(n2m) 所以需要优化 我们可以发现dp的第3维是找比它小的数,那么就可以用树状数组来找 这样就可以降低复杂度 #include<iostream> #include<cstdio> #include<cstring> #include

(POJ 3067) Japan (慢慢熟悉的树状数组)

Japan Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 29295   Accepted: 7902 Description Japan plans to welcome the ACM ICPC World Finals and a lot of roads must be built for the venue. Japan is tall island with N cities on the East coas

【二维树状数组】See you~

https://www.bnuoj.com/v3/contest_show.php?cid=9148#problem/F [题意] 给定一个矩阵,每个格子的初始值为1.现在可以对矩阵有四种操作: A x y n1 :给格点(x,y)的值加n1 D x y n1: 给格点(x,y)的值减n1,如果现在格点的值不够n1,把格点置0 M x1 y1 x2 y2:(x1,y1)移动给(x2,y2)n1个 S x1 y1 x2 y2 查询子矩阵的和 [思路] 当然是二维树状数组 但是一定要注意:lowbi

Vijos P1066 弱弱的战壕【多解,线段树,暴力,树状数组】

弱弱的战壕 描述 永恒和mx正在玩一个即时战略游戏,名字嘛~~~~~~恕本人记性不好,忘了-_-b. mx在他的基地附近建立了n个战壕,每个战壕都是一个独立的作战单位,射程可以达到无限(“mx不赢定了?!?”永恒[email protected][email protected]). 但是,战壕有一个弱点,就是只能攻击它的左下方,说白了就是横纵坐标都不大于它的点(mx:“我的战壕为什么这么菜”ToT).这样,永恒就可以从别的地方进攻摧毁战壕,从而消灭mx的部队. 战壕都有一个保护范围,同它的攻击

CF 313 DIV2 B 树状数组

http://codeforces.com/contest/313/problem/B 题目大意 给一个区间,问你这个区间里面有几个连续相同的字符. 思路: 表示个人用树状数组来写的...了解了树状数组的本质就行了. 当然用sum[r]-sum[l]也是可以的

Hdu5032 极角排序+树状数组

题目链接 思路:参考了题解.对询问进行极角排序,然后用树状数组维护一下前缀和即可. /* ID: onlyazh1 LANG: C++ TASK: test */ #include<bits/stdc++.h> using namespace std; #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 typedef long long ll; const int maxn=1010; const int maxm=10

Curious Robin Hood(树状数组+线段树)

1112 - Curious Robin Hood    PDF (English) Statistics Forum Time Limit: 1 second(s) Memory Limit: 64 MB Robin Hood likes to loot rich people since he helps the poor people with this money. Instead of keeping all the money together he does another tri

【初识——树状数组】 区间求最值

说树状数组其实是一个索引表,但是是一个特殊的,树状的索引表,它利用了二进制的一些特性. 就区间求和的要求来说: 首先我们用a[]数组来存储原始数据.然后在a[]之上构造c[]数组来作为树状数组. 如图 这个图表示,当i为奇数时,c[i]中保存的都是a[i]本身.然后,c[2]中保存了a[1], a[2],共2个,c[4]中保存的是a[1], a[2], a[3], a[4],c[6]又是保存两个,c[5]和c[6].c[8]保存8个,c[1], c[2], c[3], c[4], c[5], c

树状数组求区间最大值

------  一直用 线段树 求区间最大值,想换种思路,用树状数组试试,肯定是可以的. 首先要对 树状数组的每个 i 所管理的区间有一定的理解.详见上篇博客: 树状数组(BIT)

HOJ1867 经理的烦恼【树状数组】

题目链接: http://acm.hit.edu.cn/hoj/problem/view?id=1867 题目大意: 有C家连锁店,编号1~C,有N条指令,每家店初始的商品数目都是M.接下来N行是命令. 命令0:0 x w,连锁店x的商品数量变化为w,w > 0商品数量增加,w < 0商品数量减少. 命令1:1 x y,询问编号区间为[x,y]的连锁店商品为素数的商店有多少家. 思路: 因为区间比较大,所以用树状数组来做.用一个数组Shop[]来存放每家店的商品数目,Tree[] 表示树状数组