开始学习树状数组了,还是有些小激动的。
P.s.本文持续更新
-----------------------------------------------------------------------------
有以下问题
已知一个长为N的数列,你需要进行下面两种操作共M次:
1.将某一个数加上x
2.求出某区间每一个数的和
数据范围:N<=500000,M<=500000
怎么做呢?暴力对数组操作?这是可行的,但是N和M都大的可怕,怕是要运行到猴年马月。
既然无法暴力,那该怎么做呢?这个时候,救世主登场了,它就是:树状数组
树状数组是什么个东西呢?
树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。这种数据结构(算法)并没有C++和Java的库支持,需要自己手动实现。在Competitive Programming的竞赛中被广泛的使用。树状数组和线段树很像,但能用树状数组解决的问题,基本上都能用线段树解决,而线段树能解决的树状数组不一定能解决。相比较而言,树状数组效率要高很多。
这次百度终于讲的不是那么玄学了,传送门,可以一看。
如上图,是一个树状数组的基本结构,每一个紫色长条就是树状数组的一个节点。用来维护一些值。
为什么叫树状数组“树状”呢,是因为树状数组本就是个没有右儿子的二叉树。
不信?下图可能会更直观一些
这样子省去右儿子好处主要有两点
1、节省空间,查询方便
2、查询速度加快
坏处主要有一点
1、相比于线段树有局限性,换句话说,在一些题目上无法使用
真正的原理实现请戳这。//讲的超级好!!
再安利一个讲得超级好的网站:传送门(是英文的)
除此之外,树状数组还有一个坑点:求和时sum函数里如果i=0,会死循环
总的来说树状数组一般适用于三类问题:
1,修改一个点求一个区间(点对区间)
2,修改一个区间求一个点(区间对点)
3,求逆序列对以及其他特殊情况
注意:树状数组常常是以一种优化手段出现的,而非主要考点。
此处给出代码:
#include <cstdio> #include <algorithm> using namespace std; const int maxn=10000; int bit[maxn+1],n; int sum(int i) { int s=0; while(i>0) { s+=bit[i]; i-=i&-i;//lowbit } return s; } void add(int i,int x) { while(i<=n) { bit[i]+=x; i+=i&-i; } } int main() { /*do something*/ }
-------------------------------------以下是练习题---------------------------------
洛谷P3374 【模板】树状数组 1
分析:没错,裸的模板题。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 const int maxn=500050; 5 int bit[maxn+1],n; 6 long long sum(int i) 7 { 8 long long s=0; 9 while(i>0) 10 { 11 s+=bit[i]; 12 i-=i&-i; 13 } 14 return s; 15 } 16 void add(int i,int x) 17 { 18 while(i<=n) 19 { 20 bit[i]+=x; 21 i+=i&-i; 22 } 23 } 24 int main() 25 { 26 int m,d,x,y,p; 27 scanf("%d%d",&n,&m); 28 for(int i=1;i<=n;i++) 29 { 30 scanf("%d",&p); 31 add(i,p); 32 } 33 for(int i=0;i<m;i++) 34 { 35 scanf("%d%d%d",&d,&x,&y); 36 if(d==1) add(x,y); 37 else printf("%lld ",sum(y)-sum(x-1)); 38 } 39 return 0; 40 }
洛谷P3368 【模板】树状数组 2
分析:题解里kanate_saikou讲得非常好,推荐一看。思想是用差分。坑点:要快读,不然会Tle 3个点
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 using namespace std; 5 const int maxn=500005; 6 int bit[maxn],n,m,a[maxn]; 7 int in() 8 { 9 int res=0,x=1; 10 char c=' '; 11 while((c<'0'||c>'9') && (c!='-')) c=getchar(); 12 if(c=='-') x=-1; 13 else res=c-'0'; 14 while((c=getchar())>='0'&&c<='9') 15 { 16 res=(res<<3)+(res<<1)+c-'0'; 17 } 18 return res*x; 19 } 20 int sum(int i) 21 { 22 int sum=0; 23 while(i>0) 24 { 25 sum+=bit[i]; 26 i-=i&-i; 27 } 28 return sum; 29 } 30 int add(int i,int x) 31 { 32 while(i<=n) 33 { 34 bit[i]+=x; 35 i+=i&-i; 36 } 37 } 38 int main() 39 { 40 n=in();m=in();//printf("%d %d",n,m); 41 for(int i=1;i<=n;++i) { 42 a[i]=in(); 43 add(i,a[i]-a[i-1]); //直接将树状数组存为差分后数列 #**b[i]=a[i]-a[i-1]**# 44 } 45 for(int p=1;p<=m;++p) { 46 int op,x,y,k; 47 op=in(); 48 if(op==1) { 49 x=in();y=in();k=in(); 50 add(x,k); add(y+1,-k); // #**b[x]+=d,b[y+1]-=d**# 51 } 52 if(op==2) { 53 x=in(); 54 int ans=sum(x); //#**bit[i]=sigma(k=1 to i) b[k]**# 55 cout<<ans<<endl; 56 } 57 } 58 return 0; 59 }