树状数组(Binary Indexed Tree)

本文介绍了树状数组的数据结构,重点阐述了其在高效维护前缀和、单点修改和区间查询中的应用,包括改变数值、求区间和以及在动态规划和差分数组中的应用实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

树状数组是一种数据结构,用于高效地维护一个数组的前缀和。

它可以在O(logn)的时间内完成单点修改和区间查询操作。树状数组的核心思想是将数组分解成若干个小区间,每个小区间的和可以通过前面小区间的和计算得到。这样就可以通过二进制位运算来快速定位每个小区间的位置,从而实现高效的单点修改和区间查询操作。在引用中的代码中,树状数组被用来计算差分数组的前缀和,从而实现了高效的区间查询操作。

树状数组的存储方式是对每个数字转化为二进制,以末尾0的个数为层数创建一棵树,每个节点等于从这个数开始往前加lowbit()个数。

注:lowbit是通过位运算来求解二进制数末尾0的个数。

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
int lowbit(int t) {return t&(-t);}
int main(){
	return 0;
} 

树状数组主要分为两种操作:

一.改变一个数的大小

我们发现一个数字不断的加上他的lowbit()直到n为止,就是树状数组中所有包含这个数的前缀和。

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
int c[],n;
int lowbit(int t) {return t&(-t);}
void xg(int x,int y){
 	for(int i=x;i<=n;i+=lowbit(i)){
 		c[i]+=y;
    }
}
int main(){
	return 0;
} 

二.求l到r区间的和

同样运用前缀和的方法,用c[r]-c[l-1]。从1一直加到一个数的和正好是这个数不断的减去他的lowbit()直到1。

代码如下:
 

#include<iostream>
#include<algorithm>
using namespace std;
int c[],n,r,l;
int lowbit(int t) {return t&(-t);}
long long cx(int t){
	long long sum=0;
	for(int i=t;i;i-=lowbit(i)){
		sum+=c[i];
	}
	return sum;
}
int main(){
	long long sum=cx(r)-cx(l-1);//从r到l的区间和 
	return 0;
} 

例题:

给定一个长度为n的序列,进行q次操作,每一次可以选择个一个数加上一个x,也可以求任意一个区间的和。

思路:使用树状数组可以完美解决改变数字的前缀和问题。

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e6+5;
int n,q,l,r,x;
long long c[N];
int lowbit(int t) {return t&(-t);}
void xg(int x,int y){
	for(int i=x;i<=n;i+=lowbit(i)){
		c[i]+=y;
	}
}
long long cx(int t){
	long long sum=0;
	for(int i=t;i;i-=lowbit(i)){
		sum+=c[i];
	}
	return sum;
}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&x);
		xg(i,x);
	}
	while(q--){
		scanf("%d%d%d",&x,&l,&r);
		if(x==2){
			printf("%lld\n",cx(r)-cx(l-1));
		}
		else{
			xg(l,r);
		}
	}
	return 0;
} 

三.求最长上升子序列

题意:给定n个数,求最长上升子序列。

思路:这道题的正常思路是使用动态规划:c[i]=c[j]+1(a[i]>a[j]),但是每次都要去前面枚举最大的c[j],因此我们采用树状数组来做,每个操作有些改动。

树状数组原来是求和,但我们把改成最大值,那么两个基本函数如下:

xg函数,用于插入一个数字:
 

#include<iostream>
#include<algorithm>
using namespace std;
int lowbit(int x){ return x&(-x); }
void xg(int x,long long y){
	for(int i=x;i<=maxx;i+=lowbit(i)){
		c[i]=max(c[i],y);
	}
}
int main(){
	return 0;
}

cx函数,用来找一个数,这里用来找最大值。

#include<iostream>
#include<algorithm>
using namespace std;
int lowbit(int x){ return x&(-x); }
int cx(int x){
	long long sum=0;
	for(int i=x;i;i-=lowbit(i)){
		sum=max(sum,c[i]);
	}
	return sum;
}
int main(){
	return 0;
}

写法如下:
 

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e6+5;
long long n,a[N],c[N],maxx;
int lowbit(int x){ return x&(-x); }
void xg(int x,long long y){
	for(int i=x;i<=maxx;i+=lowbit(i)){
		c[i]=max(c[i],y);
	}
}
int cx(int x){
	long long sum=0;
	for(int i=x;i;i-=lowbit(i)){
		sum=max(sum,c[i]);
	}
	return sum;
}
int main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		a[i]++;
		maxx=max(maxx,a[i]);
	}
	for(int i=1;i<=n;i++){
		long long ans=cx(a[i]-1);
		xg(a[i],ans+1);
	}
	printf("%lld",cx(maxx));
	return 0;
}

四.BIT-2

题意:给定一个n个数的数组a,经行q次操作:1.将al-ar每个数都加上一个k;2.求ak。

思路:区间增加数字,可以使用差分的思路,差分数组想要还原到原数组需要求前缀和,但每次要维护,所以可以使用树状数组来维护,具体思路如下:


树状数组的基本实现:

#include<iostream>
#include<algorithm>
using namespace std;
int lowbit(int x){ return x&(-x); }
void xg(int x,long long y){
	for(int i=x;i<=n;i+=lowbit(i)){
		c[i]+=y;
	}
}
long long cx(int x){
	long long sum=0;
	for(int i=x;i;i-=lowbit(i)){
		sum+=c[i];
	}
	return sum;
}
int main(){
	return 0;
}

差分实现:
 

#include<iostream>
#include<algorithm>
using namespace std;
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		b[i]=a[i]-a[i-1];
		xg(i,b[i]);
	}
	while(q--){
		scanf("%d",&k);
		printf("%lld\n",cx(k));
	}
	return 0;
}

总代码:
 

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e6+5;
int n,q,a[N];
long long b[N],c[N];
itn o,l,r,k;
int lowbit(int x){ return x&(-x); }
void xg(int x,long long y){
	for(int i=x;i<=n;i+=lowbit(i)){
		c[i]+=y;
	}
}
long long cx(int x){
	long long sum=0;
	for(int i=x;i;i-=lowbit(i)){
		sum+=c[i];
	}
	return sum;
}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		b[i]=a[i]-a[i-1];
		xg(i,b[i]);
	}
	while(q--){
		scanf("%d",&k);
		printf("%lld\n",cx(k));
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值