一、引入和概念
平常我们会遇到一些对数组进行维护查询的操作,比较常见的,修改某点的值、求某个区间的和。
数据规模不大的时候,对于修改某点的值是非常容易的,复杂度是O(1),但是对于求一个区间的和就要扫一遍了,复杂度是O(N)。
如果实时的对数组进行M次修改或求和,最坏的情况下复杂度是O(M*N),当规模增大后这是划不来的。
而树状数组干同样的事复杂度却是O(M*lgN)。
树状数组是一个查询和修改复杂度都为log(n)的数据结构。
主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
看完概念后发现和线段树的功能类似,实际上树状数组和线段树确实类似,不过也有不同,具体区别和联系如下:
1.两者在复杂度上同级, 但是树状数组的常数明显优于线段树, 其编程复杂度也远小于线段树.
2.树状数组的作用被线段树完全涵盖, 凡是可以使用树状数组解决的问题, 使用线段树一定可以解决, 但是线段树能够解决的问题树状数组未必能够解决.
3.树状数组的突出特点是其编程的极端简洁性, 使用lowbit技术可以在很短的几步操作中完成树状数组的核心操作,与之相关的便是其代码效率远高于线段树。
二、实现
树状数组,重点是在树状的数组
一颗普通的二叉树如下
叶子结点代表A数组A[1]~A[8]
现在变形一下
现在定义每一列的顶端结点C[]数组 ,如下图
C[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];
其中C数组的求法如下:
C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i]; (k为i的二进制中从最低位到高位连续零的长度)例如i=8时,k=3;
为了更好的理解上面的公式,将1-32的2^k计算出来如下:
有了这个表格之后对照上面的式子就可以轻松的知道每一个C代表的哪几个数的和。比如
C[8],由上图知2^k为8,那么
C[24],由上图知2^k为8,那么
这是我们通过简单的计算得出来的值,那么应该如何转换成编程语言呢,大神们给出了非常巧妙的方法,利用下面的函数可以求出2^k的值。
int lowbit(int x) { return x&-x; }
为什么这样可以呢?这里复制了一篇证明可以看一下。
首先明白一个概念,计算机中-i=(i的取反+1),也就是i的补码
而lowbit,就是求(树状数组中)一个数二进制的1的最低位,例如01100110,lowbit=00000010;再例如01100000,lowbit=00100000。
所以若一个数(先考虑四位)的二进制为abcd,那么其取反为(1-a)(1-b)(1-c)(1-d),那么其补码为(1-a)(1-b)(1-c)(2-d)。
如果d为1,什么事都没有-_-|||但我们知道如果d为0,天理不容2Σ( ° △ °|||)︴
于是就要进位。如果c也为0,那么1-b又要加1,然后又有可能是1-a……直到碰见一个为补码为0的bit,我们假设这个bit的位置为x
这个时候可以发现:是不是x之前的bit的补码都与其自身不同?,x之后的补码与其自身一样都是0?
例如01101000,反码为10010111,补码为10011000,可以看到在原来数正数第五位前,补码的进位因第五位使其不会受到影响,于是0&1=0,;
但在这个原来数“1”后,所有零的补码都会因加1而进位,导致在这个“1”后所有数都变成0,再加上0&0=0,所以他们运算结果也都是零;
只有在这个数处,0+1=1,连锁反应停止,所以这个数就被确定啦O(∩_∩)O
有了上面的基础,我们就可以解决很多问题了。重要的操作有两个,分别是更新和求和。
1.更新操作
void update(int k,int x) { for(int i=k;i<=n;i+=lowbit(i)) C[i]+=x; }
2.求和操作
int getsum(int x) { int ans=0; for(int i=x;i;i-=lowbit(i))//i要大于0 ans+=C[i]; return ans; }
三、代码
#include <iostream> #include <cstdlib> #include <algorithm> #include <string> #include <cstring> #include <stdio.h> #include <queue> #define IO ios::sync_with_stdio(false);\ cin.tie(0); cout.tie(0); using namespace std; #define N 50100 int n; int c[N]; int lowbit(int x) { return x&(-x); } void update(int k,int x) { for(int i=k; i<=n; i+=lowbit(i)) c[i]+=x; } int getsum(int x) { int ans=0; for(int i=x; i; i-=lowbit(i)) //i要大于0 ans+=c[i]; return ans; } int main() { IO; int T; cin>>T; int logo=1; while(T--) { memset(c,0,sizeof(c)); cin>>n; for(int i=1; i<=n; i++) { int t; cin>>t; update(i,t); } char s[100]; cout<<"Case "<<logo++<<":"<<endl; while(1) { cin>>s; if(s[0]==‘E‘) break; if(s[0]==‘Q‘) { int a,b; cin>>a>>b; cout<<getsum(b)-getsum(a-1)<<endl; } if(s[0] == ‘S‘) { int a,b; cin>>a>>b; update(a,-b); } if(s[0] == ‘A‘) { int a,b; cin>>a>>b; update(a,b); } } } return 0; }
原文地址:https://www.cnblogs.com/aiguona/p/8278846.html