树状数组

Binary Indexed Tree(树状数组)的概念

树状数组是能够完成下述操作的数据结构。
给定一个初始值全为0的数列a1,a2,···,an

  • 给定i,计算a1+a2+···+ai
  • 给定i和x,执行ai+=x

基于线段树的实现

如果使用线段树,只需要对前一节的RMQ的样例作少许修改就可以实现这两个功能。即线段树的每个节点上维护的是对应区间的和。
接下来,我们来看如何求得对应区间的和(s到t的和),对于线段树来说,是可以直接求得对应区间的和的。
但是如果我们能够计算(从1到t的和)-(从1到s-1的和),同样可以求得s到t的和。也就是说,对于任意的i,我们能计算出从1到i的部分和就够了。
在这样的限制下,会带来怎样的改变呢?我们可以发现,线段树上每个节点的右儿子都不需要了(在计算时如果要使用这个点的值,那么它的左边的兄弟的值也一定会用到,这个时候只需要使用它父亲的值就好了)。
基于以上的思路得到的数据结构就是BIT。

BIT的结构

也就是把线段树中不需要的节点去掉之后,再把剩下的节点对应到数组中。让我们对比每个节点对应的区间的长度和节点编号的二进制表示。以1结尾的1,3,5,7的长度为1,最后有1个0结尾的2,6,的长度是2,最后有2个0结尾的4长度是4······这样,编号的二进制表示就能够和区间非常容易地利用起来,利用这个性质,BIT可以通过非常简单地位运算实现。

BIT的求和

计算前i项的和需要从i开始,不断把当前位置的i的值加到结果中,并从i中减去i的二进制最低非0位对应的幂,知道i变成0为止。i的二进制的最后一个1可以用i&-i表示。

BIT的更新

使第i项的值增加x需要从i开始,不断把当前为止i的值增加x,并把i的二进制最低非0位对应的幂加到i上。

BIT的复杂度

总共需要对O(logn)个值进行操作,所以复杂度为O(logn)

区间和线段树求法

#include<iostream>
using namespace std;
const int SZ_TREE=1<<15;
int Tree[SZ_TREE<<1];
int N,n;
void init(){
	n=1;
	while(n<N)n*=2;
	for(int i=0;i<2*n-1;i++)Tree[i]=0;
}
void update(int k,int x){
	k+=n-1;
	Tree[k]+=x;
	while(k>0){
		k=(k-1)/2;
		Tree[k]=Tree[k*2+1]+Tree[k*2+2];
	}
}
int query(int id,int a,int b,int l,int r){
	if(b<=l||r<=a)return 0;
	if(a<=l&&r<=b)return Tree[id];
	else{
		int x=query((id<<1)+1,a,b,l,(l+r)>>1);
		int y=query((id<<1)+2,a,b,(l+r)>>1,r);
		return x+y;
	}
}
int main(){
	cin >> N;
	init();
	int x;
	for(int i=0;i<N;i++){
		cin >> x;
		update(i,x);
	}
	int C;
	int y,z;
	cin >> C;
	for(int i=0;i<C;i++){
		cin >> x;
		if(x==1){//1是查询 0是增加 
			cin >> y >> z;
			cout << query(0,y-1,z,0,n) << endl;
		}
		else {
			cin >> y >> z;
			update(y-1,z);
		}
	}
	return 0;
}

树状数组写法

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=10000;
int Tree[maxn];
int N;
void update(int k,int x){
	while(k<=N){
		Tree[k]+=x;
		k+=k&(-k);
	}
}
int query(int k){
	int s=0;
	while(k>0){
		s+=Tree[k];
		k-=k&(-k);
	}
	return s;
}
int main(){
	cin >> N;
	int x;
	for(int i=1;i<=N;i++){
		cin >> x;
		update(i,x);
	}
	int C;
	cin >> C;
	int y,z;
	for(int i=0;i<C;i++){
		cin >> x;
		if(x==1){
			cin >> y >> z;
			cout << query(z)-query(y-1) << endl;
		}
		else{
			cin >> y >> z;
			update(y,z);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值