树状数组算法讲解及例题

核心思想

每个位置的覆盖范围由二进制最低位的 1 1 1决定。
例如:
索引 6 6 6(二进制为 110 110 110) 管理 2 2 2个元素( 5 5 5 6 6 6
索引 8 8 8(二进制为 1000 1000 1000) 管理 8 8 8个元素( 1 1 1- 8 8 8
那这个覆盖范围就是最低位的 1 1 1来决定,说人话就是这个数的二进制从右往左数第一个 1 1 1为最高位,找过的 0 0 0一次排在次低位的数。对 x x x找这样的数称为 l o w b i t ( x ) lowbit(x) lowbit(x)
比如说 6 ( 11 0 2 ) 6(110_2) 6(1102),他的 l o w b i t lowbit lowbit值就是 2 ( 1 0 2 ) 2(10_2) 2(102)
8 ( 100 0 2 ) 8(1000_2) 8(10002),整个二进制就开头一个 1 1 1,所以 l o w b i t ( 8 ) lowbit(8) lowbit(8)就是他自己。
而这个 l o w i b t lowibt lowibt值的求法就是按位与自己的相反数

int lowbit(int x){
	return x&-x;
}

然后树状数组的核心代码就完了。

更新操作

单点修改

比如说要对点 x x x加上 k k k,那么和线段树的思想差不多,当子节点被改动时,上层节点的值也会变动,怎么找到上层节点呢? x x x的上层节点就是 l o w b i t ( x ) lowbit(x) lowbit(x),所以改完点 x x x的值之后,就应该改动点 l o w b i t ( x ) lowbit(x) lowbit(x)的值,使其一直维护区间和。一直往上找,直到找到了一个点包含整个数组的区间和(根节点)为止。

void change(int x,int k){
	while(x<=n){
		f[x]+=k;
		x+=lowbit(x);
	}
}
区间查询

树状数组的区间查询类似前缀和,是
如果我们要查询 a 1 a_1 a1 ~ a x a_x ax的和,先看一下核心思想部分,那个部分讲过点 x x x的覆盖范围是 l o w b i t ( x ) lowbit(x) lowbit(x),也就是说在大小为 l o w b i t ( x ) lowbit(x) lowbit(x)的范围内,这个区间的点之和是被点 x x x包含的,所以这个区间的元素和就存在点 x x x之中,不用再计算,那么我们就要减去点 x x x所覆盖的区间,查找下一个区间,所以就在统计完之后用 x x x减去 l o w b i t ( x ) lowbit(x) lowbit(x)。直到剪完了, 1 1 1 ~ x x x都查完了为止。

int query(int x){
	int res=0;
	while(x){
		res+=f[x];
		x-=lowbit(x);
	}
	return res;
}

然后如果要查找区间 l l l ~ r r r,就想前缀和那样,用 q u e r y ( r ) query(r) query(r)减去 q u e r y ( l − 1 ) query(l-1) query(l1)就行了。
整个树状数组的主要函数就讲完了。

模版题:P3374【模板】树状数组 1

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int a[N];
int bit[N];
int n,m;
int lowbit(int x){
	return x&-x;
}
void change(int x,int k){
	while(x<=n){
		bit[x]+=k;
		x+=lowbit(x);
	}
}
int query(int x){
	int res=0;
	while(x){
		res+=bit[x];
		x-=lowbit(x);
	}
	return res;
}
signed main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		change(i,a[i]);//将原数组存入树状数组
	}
	while(m--){
		int op,l,r;
		cin>>op>>l>>r;
		if(op==1){
			change(l,r);
		}
		else{
			cout<<query(r)-query(l-1)<<'\n';
		}
	}
}

例题2

接下来再来看另一道题:P3368 【模板】树状数组 2
区间修改,阁下又该如何应对

思路

但是这道题是点查询,所以我们不用写真正的区间修改。
我们只需要用树状数组维护修改的内容就行了。

change(l,k);
change(r+1,-k);

至于点查询,就是用 q u e r y ( x ) query(x) query(x)减去 q u e r y ( x − 1 ) query(x-1) query(x1)就行了。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int a[N];
int f[N];
int n,m;
int lowbit(int x){
	return x&-x;
}
void change(int x,int k){
	while(x<=n){
		f[x]+=k;
		x+=lowbit(x);
	}	
}
int query(int x){
	int res=0;
	while(x){
		res+=f[x];
		x-=lowbit(x);
	}
	return res;
}
signed main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	while(m--){
		int op,l,r,k;
		cin>>op>>l;
		if(op==1){
			cin>>r>>k;
			change(l,k);
			change(r+1,-k);
		}
		else{
			cout<<a[l]+query(l)<<'\n';
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值