树状数组(瞎bb) [树状数组]

Copyright:http://www.cnblogs.com/ZYBGMZL/

树状数组是一个利用一维数组和位运算组成的求解区间问题的高效数据结构,其构造如图所示

首先,我们要用它解决单点修改、区间查询的操作。

根据这张图我们建立一个数组bit[],下标就是图中显示的十进制数。bit[i]就表示了图中所示的一段区间的和,例如bit[6]=sum(5,6),bit[4]=sum(1,4)。

下标最大值为序列的长度n。

我们接下来要求一段序列中left到right的和,就可以转化为求sum(1,right)-sum(1,left)。

那么对于已知的x,怎么求sum(1,x)呢?

在这样一个栗子(上图)中,我们需要求得sum(1,6)的值,可以将其转化为求bit[6]+bit[4]。

显而易见,只要将6的二进制数0110的最低位1删去,就得到0100,对应了10进制的4。

再举一个栗子(如上图),我们想求sum(1,3)的值,只要求bit[2]+bit[3]就好了。

变化规律和上一个一样。(0011---->0010)

对于多段区间,也满足一样的规律(上图)。

我们总结,对于x,只要不断减去x中最低位的1,将得到的bit[x]相加就可以得到结果了。

用这样一个方法获取x的最低位1

x&-x

就得到了这样一个操作代码

1 int sum(int x){
2     int sum=0;
3     while(x){
4         x+=bit[x];
5         x-=x&-x;
6     }
7     return sum;
8 }

 

 

查询的问题解决了,接下来要处理修改的操作了。

举个栗子,假如对3点进行加a的操作,在bit[3]修改的同时,bit[4]和bit[8]也要相应修改。

观察这3个数(3,4,8)的二进制数,可以发现,只要不断地加上其最低位的1,就可以完成操作了。

0011+0001=0100

0100+0100=1000

再对所得的数判断是否大于n即可。

于是我们得到了这样一个单点修改的操作代码。

1 void add(int x,int a){
2     while(x<=n){
3         bit[x]+=a;
4         x+=x&-x;
5     }
6 }

 

以上是树状数组操作最简单的模型,除此之外,树状数组还有区间修改、单点查询,以及区间修改、区间查询的功能。

区间修改、单点查询

 1 //改段求点型操作 
 2 
 3 //b[i]表示区间1...i一共变化的量 
 4 int b[101];
 5 //原始数组 
 6 int a[101];
 7 
 8 void ADD(int x, int c)
 9 {
10      for (int i=x; i>0; i-=i&(-i)) 
11          b[i] += c;
12 }
13 
14 void add(int left,int right,int c){
15     if(left-1)  ADD(left-1,-c);
16     ADD(right,c);
17 }
18 
19 int SUM(int x)
20 {
21     int s = 0;
22     for (int i=x; i<=n; i+=i&(-i)) 
23         s += b[i];
24     return s+a[x];
25 }

区间修改、区间查询

 1 //改段求段型:
 2 //这是最复杂的模型,需要两个辅助数组:B[i]表示a[1..i]到目前为止共被整体加了多少(和模型2中的一样)
 3 //C[i]表示a[1..i]到目前为止共被整体加了多少的总和(或者说,C[i]=B[i]*i)。
 4 
 5 //对于ADD(x, c),只要将B[x]加上c,同时C[x]加上c*x即可(根据C[x]和B[x]间的关系可得);
 6 
 7 //而ADD(x, c)操作是这样影响a[1..i]的和的:若x小于i,则会将a[1..i]的和加上x*c,
 8 //否则(x>=i)会将a[1..i]的和加上i*c。也就是,a[1..i]之和 = B[i..N]之和 * i + C[1..i-1]之和。 
 9 //这样对于B和C两个数组而言就变成了“改点求段”(不过B是求后缀和而C是求前缀和)。 
10 //另外,该模型中需要特别注意越界问题,即x=0时不能执行SUM_B操作和ADD_C操作。
11 
12 
13 //【1】修改操作:将A[l..r]之间的全部元素值加上c;
14 //ADD_B(r, c); ADD_C(r, c);
15 //if (l > 1) {ADD_B(l - 1, -c); ADD_C(l - 1, -c);}
16 //【2】求和操作:求此时A[l..r]的和。
17 //SUM(r) - SUM(l - 1)。
18 
19 //B[i]表示区间1...i变化量 
20 int B[101];
21 //C[i]表示区间1...i变化量的总和,有c[i]=b[i]*i 
22 int C[101];
23 
24 void ADD_B(int x, int c)
25 {
26      for (int i=x; i>0; i-=i&(-i)) B[i] += c;
27 }
28 
29 void ADD_C(int x, int c)
30 {
31      for (int i=x; i<=n; i+=i&(-i)) C[i] += x * c;
32 }
33 
34 int SUM_B(int x)
35 {
36     int s = 0;
37     for (int i=x; i<=n; i+=i&(-i)) s += B[i];
38     return s;
39 }
40 
41 int SUM_C(int x)
42 {
43     int s = 0;
44     for (int i=x; i>0; i-=i&(-i)) s += C[i];
45     return s;
46 }
47 
48 int SUM(int x)
49 {
50     if (x) return SUM_B(x) * x + SUM_C(x - 1); else return 0;
51 }

 

posted @ 2017-05-23 15:48  ZYBGMZL  阅读(356)  评论(0编辑  收藏  举报