【数据结构】树状数组 萌新的树状数组学习之路

首先从废话引入

在介绍树状数组前,我们先观察这样一个标准的树状数组板子题:【洛谷】--树状数组1

image

在不考虑数据范围的前提下,我们可以想到暴力的算法:使用数组存数,每次修改单点修改,每次查询都遍历一次
这样我们的时间复杂度:修改:O(1),查找O(M*N)

//存数
int n,m;cin>>n>>m;
int arr[N];
for(int i=0;i<n;i++)
	cin>>arr[i]; 

//修改
cin>>i>>x;
arr[i]+=x;

//查询
int sum=0;
for(int i=l;i<=r;i++)
	sun+=arr[i];
cout<<sum<<endl; 

显然这样的查找方式有些太慢,于是我们可以使用一个数组维护前缀和,从而使查询的时间复杂度降为O(1),可相应的,修改的时间复杂度又变为了O(N)

//存数
int n,m;cin>>n>>m;
int arr[N];
int c[N];//另开一个数组维护前缀和 
for(int i=0;i<n;i++){
	cin>>arr[i];
	c[i]=arr[i];
	if(i>0)c[i]+=c[i-1]; 
} 
	
//修改
cin>>i>>x;
while(i){
	c[--i]+=x;
}

//查询
cin>>l>>r;
cout<<c[r]-c[l]<<endl; 

然而考虑到洛谷给的数据范围,显然我们不能用暴力的方式水过:

【数据范围】
对于 30% 的数据,1n81m10
对于 70% 的数据,1n,m104
对于 100% 的数据,1n,m5×105

此时我们就要用树状数组来进行优化了,树状数组是一种用于维护区间增删改查的数据结构,通过二进制优化前缀的方式,将修改和查找的时间复杂度都保持在O(logN)

首先我们来看两种暴力思路各自的优劣
直接暴力 便于修改,可是查找需要遍历整个区间
前缀和 查找只需要进行两个区间和相减,但是修改时需要遍历整个区间来修改区间中每个位置的对应前缀和
我们知道程序的运行时间取决于各部分运行时间最长的部分,因此以上两种方式都是O(N)

那我们最好是有一种方法,可以同时较快的修改和查找(废话qwq),此时我们想到可以借助分块的思想,将每一个区间元素分成若干个块,再通过维护各自分块来查询和修改的时间复杂度。

关于如何划分,我们就要引入lowbit()了

lowbit

说lowbit是树状数组的灵魂一点也不为过,虽说我们可以很轻易的写出代码实现,但在此我们先介绍其原理

点击查看代码
int lowbit(int x){
	return x&(-x);
}

首先我们将前缀和数组的下标写作二进制的形式,可以得出如下形式:
1 -> 1
2 -> 10
3 -> 11
4 -> 100
5 -> 101
6 -> 110
7 -> 111
8 -> 1000

我们可以看出,当我们将数字N写成二进制时,这个二进制数的位数是不会大于logN+1的,因此我们可以通过二进制分解下标的方式来确定我们要如何划分
我们将每个二进制位

posted @   ~Chitoge  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示