树状数组的基操
前沿:数据结构
任何一个数据结构都逃不开这么几个东西:增,删,改,查
-------------------------------------------------------------------
树状数组,也叫做二叉索引树(BIT)。
是一个用来进行区间运算的数据结构。
在一定程度上可以代替线段树。
树状数组有以下几个特征:
1.巧妙地利用了位运算
2.巧妙地结合了树的数据结构的思想来处理区间问题
3.树状数组的本质还是用来维护序列的前缀
--------------------------------------------------------------------
树状数组有以下几个基本操作:
1.树的节点转移(位运算)//是2,3,4,的根基
2.单点修改
3.区间修改
4.区间查询
--------------------------------------------------------------------
首先树状数组的大致情况,我给出了一下资料:
算法竞赛入门经典-训练指南-第194-197页
--------------------------------------------------------------------
树状数组的时间复杂度和空间复杂度:
1、时间复杂度:
O(logn),这是按照一次更改或者查询来讲的(局部)
O(nlogn),这是按照更新所有n个值来讲的(整体)
2、空间复杂度:
O(n)(一维)
O(n*m)(二维)
--------------------------------------------------------------------
接下来就是以上操作的具体实现的代码:
等等,先给出一些数组
c[]表示树状数组,a[]表示原序列
好了,let‘s go
1.树的节点转移(位运算)//关于这个函数的作用和这样做的原因,在上面提到的书中有讲到
int lowbit(int x) { return x&(-x); }
2,单点修改
与线段树相同的是,树状数组如果修改了某一个点的话,势必会影响到上层区间的维护的值,那么单点修改的关键就是怎么维护上层区间的维护的值
下面我给出一个函数:
void add(int d,int x) { while(x<=n) { c[x]+=d; x+=lowbit(x); } }
3.区间修改
这个涉及到了差分数组的运用
有关差分数组和这一部分的数学证明,我在一下链接种给出:
https://www.cnblogs.com/lcf-2000/p/5866170.html
void qujian_update(int* arr,int x,int d) { while(x<=n) { arr[x]+=d; x+=lowbit(x); } }
4.区间查询
int query(int* arr,int x) { int ret=0; while(x>0) { ret+=arr[x]; x-=lowbit(x); } return ret; }
--------------------------------------------------------------------
现在,我给出一份完整的函数代码:
第一份:没有区间修改的树状数组
#include<bits/stdc++.h> using namespace std; int c[20]; int lowbit(int x)//作用:返回查找结点的下标 { return x&-x; } int sum(int x)//返回1~x的数组的前缀和 { int ret=0; while(x>0) { ret+=c[x]; x-=lowbit(x); } return ret; } void add(int x,int d)//即能够起到修改区间的作用,又能够起到构建树状数组(二叉索引树)的作用 { while(x<=n) { c[x]+=d; x+=lowbit(x); } } int query(int l,int r)//作用:查询l~r的区间和,注意:sum[l,r]=c[r]-c[l-1]; { int ret=0; ret=sum(r)-sum(l-1); return ret; } int main() { int n,a[10]; cin>>n; memset(c,0,sizeof(c)); for(int i=1;i<=n;++i) { cin>>a[i];//输入的数组 add(i,a[i]);//不能从0开始,因为0会让lowbit进入死循环 } string s; while(cin>>s) { if(s=="query")//查询的命令 { int l,r; cin>>l>>r; cout<<query(l,r)<<endl; } else if(s=="add")//修改的命令,在a[x]上加上d => a[x]=a[x]+d { int d,x; cin>>d>>x; add(x,d); } else if(s=="change")//单点更新=>a[x]=d { int d,x; cin>>d>>x; add(x,d-a[x]); } } return 0; }
第二份:带区间修改的树状数组
#include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a)) #define maxn 1200000 #define mod 123456789 using namespace std; typedef long long ll; int c1[maxn],n,c2[maxn],a[maxn]; int lowbit(int x) { return x&(-x); } void add(int d,int x) { while(x<=n) { c[x]+=d; x+=lowbit(x); } } void qujian_update(int* arr,int x,int d) { while(x<=n) { arr[x]+=d; x+=lowbit(x); } } int query(int* arr,int x) { int ret=0; while(x>0) { ret+=arr[x]; x-=lowbit(x); } return ret; } int main() { cin>>n; mem(c1,0); mem(c2,0); mem(a,0); for(int i=1;i<=n;++i) { cin>>a[i]; qujian_update(c1,i,a[i]-a[i-1]); qujian_update(c2,i,(i-1)*(a[i]-a[i-1])); } string s; while(cin>>s) { if(s=="change") { int l,r,d; cin>>l>>r>>d; qujian_update(c1,l,d); qujian_update(c1,r+1,-d); qujian_update(c2,l,d*(l-1)); qujian_update(c2,r+1,-d*r); } else { int l,r; cin>>l>>r; int tmp1=(l-1)*query(c1,l-1)-query(c2,l-1); int tmp2=r*query(c1,r)-query(c2,r); int ans=tmp2-tmp1; cout<<ans<<endl; } } return 0; }
-----------------------------------------
BIT还可以用来维护区间,还可以统计逆序数(维护比当前数字小的个数和总和)