树状数组-----入门级别
树状数组
昨日初学树状数组,写一下自己的心得,如有不对之处,欢迎指出!!!
对于树状数组,我现在的认知便是它可以用来解决区域间求和的问题,针对于那些,区间内元素可能改变的题有省时效果。比如有一串数存在a[1~n]中,要求区间i到j中元素的和,用s[i]数组保存前i项的总和,那么求区间和时间复杂度o(1),也就是s[j]-s[i-1] ,但是如果采取这种方法,那么一旦1~n中的某个值改动了,那么要更新该数后的所有s[],时间复杂度o(n)。(当然,如果不用s数组存也一样,就是查询时间变为o(n),而修改时间变为o(1)。)而如果采用树状数组保存,那么每次查询用时o(log n),每次更改也只需o(log n)的时间,总体来说,比较省时。
树状数组指的便是下图中的c数组:(a[i]便是我们研究的数组,对它进行操作)
由图可以看出:
- c[1]=a[1]
- c[2]=a[1]+a[2]
- c[3]=a[3]
- c[4]=a[1]+a[2]+a[3]+a[4]
- c[5]=a[5]
- c[6]=a[5]+a[6]
- c[7]=a[7]
- c[8]=a[1]+a[2]+ a[3]+a[4]+ a[5]+a[6]+ a[7]+a[8]
对于单数i,c[i]便等于a[i],而对于偶数就要观察它的二进制下的数了。
比如c[2]:二进制下的2为 10,可以说从右往左最多有一个连续的0,所以c[2]对应包括本身在内的左边2^1个元素,a[2]和a[1]。
对于c[4]:二进制下的4为 100,从右往左最多有两个连续的0,所以c[4]对应包括自身在内的左边2^2个元素,a[4]、a[3]、a[2]、a[1]。
同理,对于c[6],6二进制为110,有一个0,包括a[6]和a[5]两个元素。
推广一下,单数也满足这个规律,比如c[5],5二进制为101,包括2^0个元素,即a[5]。
在后面,我们用k来表示二进制下的该数末尾有几个0。
(对于这个c数组的规律我们还可以看下图,加深印象)
C数组构成一个满二叉树(a数组为叶子):对于k,我们还可以理解为这个c[i]子树的深度,而2^k则是叶子数。c[8]有两棵子树,c[4]和红框,两棵子树的叶子数自然是一样的,所以c[8]的叶子数是c[4]的两倍,c[8]的深度也比c[4]的大一。
对于2^k,这个式子是非常经常用到的,因此对于这个式子的求法已经精简到我无法想象的地步,给出两个方法1. 2^k=i&(-i) 2. 2^k=i&(i^(i-1))。
写成函数:
int Lowbit(int x)
{
return x&(-x);
}
可以看一个例子,比如i=616:二进制为:0010 0110 1000 ,k为3,2^3即为8。
(-i)为:
(反码)1101 1001 0111
(补码)1101 1001 1000
i&(-i)为:
0000 0000 1000 即十进制下的8。
其实可以发现,要求2^k,就是i二进制下的最右边的一个1和它右边所有的0。
也就是说对于0010 0110 1000,我们要做的就是将1000单独取出,其它位都归0。
如此,问题就好理解了一些,(-i)的反码首先把所有的二进制位全变得与i相反,所以i中的答案1000就变成0111,补码再加上1,自然又变回了1000,而1左边的数还是处于相反的状态,相与一下就把答案单独拿出了。
说了这么多,我们回归到我们的目的,也就是求s数组身上,c数组我们可以看做是一个纽带,它将底层的a数组和目的s数组连在一起,以后对a数组的修改和对s数组的查询便集中到了c数组上。
可以看出:
- s[1]=c[1]
- s[2]=c[2]
- s[3]=c[3]+c[2]
- s[4]=c[4]
- s[5]=c[5]+c[4]
- s[6]=c[6]+c[4]
- s[7]=c[7]+c[6]+c[4]
- s[8]=c[8]
那么总结一下:
对于s[3],先看c[3],c[3]有一个叶子a[3],那么求s[2],看c[2],c[2]有两个叶子a[1]+a[2],那么求s[0]=0,结束,s[3]=c[3]+c[2]。
对于s[7],先看c[7],c[7]有1个叶子a[7],那么求s[6],看c[6],c[6]有两个叶子a[5]+a[6],那么求s[4],看c[4],c[4]有四个叶子a[1]+a[2]+a[3]+a[4],那么求s[0]=0,结束,s[7]=c[7]+c[6]+c[4]。
写成函数如下:
int getsum(int pos)
{
int sum=0;
while(pos>0)
{
sum+=c[pos];
pos-=Lowbit(pos); //c[pos]包括了Lowbit(pos)个叶子,也就是说可以接着算s[pos- Lowbit(pos)]了。
}
return sum;
}
这便是查询的时候需要做的事了。
那么,除了查询我们还要应付的便是修改区间内的某个值,我们修改了a[i],那么就将有一系列的c[]需要修改,而这些要修改的c[]都是些什么呢,我们可以看出,经过修改遭受牵连的c数组,首先是直接联系的c[i],如图,c[i]都在a[i]的正上方,直接受到a[i]的影响,然后便是与c[i]有关的c[…]了,这些c[…]便是c[i]的祖先,比如c[2]改了,同时影响到c[4],c[8],c[16]……
举例了:
如果改了a[5],那么c[5]得改,c[5]有一个叶子,那么它的双亲即c[5+1]得改,c[6]有两个叶子,那么它的双亲c[6+2]得改。(双亲的计算原理便是前面讲过的左右子树叶子相同)
写成函数如下:
void update(int pos,int num,int n) //n—最大下标,num为改变量
{
while(pos<=n)
{
c[pos]+=num;
pos+=Lowbit(pos); //c[pos]的叶子数即为Lowbit(pos)。
}
}
这便是修改的时候要做的事了。
那么,通过树状数组,我们便把对两个不同的数组a和s要进行的操作集中到了c数组上。
下面我们来看一道题,地址:http://acm.hdu.edu.cn/showproblem.php?pid=4046
大概题意就是说:给你一字符串,仅有b和w组成,而每次可能进行的操作有两种:
0:输入i和j,输出i、j之间有多少”wbw”存在。
1:输入i和一个字符c,表示将第i位改为c。
大致思路:
可以假想有一个a数组存放着以i为中心的三个字符是否满足条件,如果是就为1,不是就为0,而c数组便是一个树状数组记录a中的内容。对于每次修改,我们简化为:本次修改是否造成wbw数量减少(原有的被破坏),又是否造成它数量的增多(创造出新的wbw)。值得注意的是,查询i到j之间的wbw数量时,要转化成查询i+1和j-1之间的,因为边缘的值不满足条件。
AC代码:
1 #include<stdio.h>
2 char s[50010];
3 int c[50010];
4 int Lowbit(int x)
5 {
6 return x&(-x);
7 }
8 void update(int pos,int num,int n)
9 {
10 while(pos<=n)
11 {
12 c[pos]+=num;
13 pos+=Lowbit(pos);
14 }
15 }
16 int getsum(int pos)
17 {
18 int sum=0;
19 while(pos>0)
20 {
21 sum+=c[pos];
22 pos-=Lowbit(pos);
23 }
24 return sum;
25 }
26 int main()
27 {
28 int i,j,g,t,n,m,k,h2,h1,x1,x2,p;
29 char ch;
30 while(scanf("%d",&t)!=EOF)
31 {
32 g=1;
33 while(t--)
34 {
35 scanf("%d%d",&n,&m);
36 scanf("%s",s+1);
37 for(i=0;i<=n;i++)
38 c[i]=0;
39 for(i=2;i<=n;i++)
40 if(s[i]=='b')
41 {
42 if((s[i-1]==s[i+1])&&s[i+1]=='w')
43 update(i,1,n);
44 }
45 printf("Case %d:\n",g++);
46 for(i=0;i<m;i++)
47 {
48 scanf("%d",&p);
49 if(p==0)
50 {
51 scanf("%d%d",&x1,&x2);
52 if(x2-x1<2)
53 {
54 printf("0\n");
55 continue;
56 }
57 h2=getsum(x2);
58 h1=getsum(x1+1);
59 printf("%d\n",h2-h1);
60 }
61 else if(p==1)
62 {
63 scanf("%d %c",&x1,&ch);
64 x1++;
65 if(s[x1]==ch) continue;
66 for(j=x1-1;j<x1+2;j++)
67 {
68 if(j>1&&j<n&&s[j]=='b'&&(s[j-1]==s[j+1])&&s[j+1]=='w')
69 update(j,-1,n);
70 }
71 s[x1]=ch;
72 for(j=x1-1;j<x1+2;j++)
73 {
74 if(j>1&&j<n&&s[j]=='b'&&(s[j-1]==s[j+1])&&s[j+1]=='w')
75 update(j,1,n);
76 }
77 }
78 }
79 }
80 }
81 return 0;
82 }