树状数组入门&树状数组进阶

题目:BIT-1

题目信息

题目描述

给定数组 a 1 , a 2 , a 3 . . . a n a_1,a_2,a_3...a_n a1,a2,a3...an进行 q q q次操作,操作有两种:
1 i x:将 a i + x a_i+x ai+x
2 l r​:求 a l + . . . + a r a_l+...+a_r al+...+ar

输入样例

3 2
1 2 3
1 2 1
2 1 3

输出样例

7

数据描述

1 ≤ n , q ≤ 1 0 6 1≤n,q≤10^6 1n,q106
∣ a i ∣ ≤ 1 0 6 |a_i|≤10^6 ai106
对于输入:保证 1 ≤ l ≤ r ≤ n , ∣ x ∣ ≤ 1 0 6 1≤l≤r≤n,|x|≤10^6 1lrn,x106

题目思路

运用树状数组求出区间和(注:树状数组只能返回前缀和,还需要处理,如: c h a x u n ( r ) − c h a x u n ( l − 1 ) chaxun(r)-chaxun(l-1) chaxun(r)chaxun(l1)

  1. 输入各值,利用修改函数,创建树状数组(详见后面“知识点:树状数组”)。
scanf("%d %d",&n,&q);
for(int i=1;i<=n;i++){
	int a;
	scanf("%d",&a);
	xiugai(i,a);
}
void xiugai(int idx,int num){
	for(int i=idx;i<=n;i+=lowbit(i)){
		c[i]+=num; 
	}
}
  1. 循环输入进行查询。
for(int i=1;i<=q;i++){
	int num,l,r;
	scanf("%d %d %d",&num,&l,&r);
}
  1. 如果是1就执行修改操作
int lowbit(int x){
	return x&-x;
}
void xiugai(int idx,int num){
	for(int i=idx;i<=n;i+=lowbit(i)){
		c[i]+=num; 
	}
}
  1. 如果是2就执行求和操作并输出操作。
if(num==1){
	xiugai(l,r);
}
else{
	printf("%lld\n",chaxun(r)-chaxun(l-1));
}
long long chaxun(int r){
	long long sum=0;
	for(int i=r;i>0;i-=lowbit(i)){
		sum+=c[i];
	}	
	return sum;
}

AC代码

#include<iostream>
#include<cstdio>
using namespace std;
int n,q;
long long c[1000005];
int lowbit(int x){
	return x&-x;
}
void xiugai(int idx,int num){
	for(int i=idx;i<=n;i+=lowbit(i)){
		c[i]+=num; 
	}
}
long long chaxun(int r){
	long long sum=0;
	for(int i=r;i>0;i-=lowbit(i)){
		sum+=c[i];
	}	
	return sum;
}
int main(){
	scanf("%d %d",&n,&q);
	for(int i=1;i<=n;i++){
		int a;
		scanf("%d",&a);
		xiugai(i,a);
	}
	for(int i=1;i<=q;i++){
		int num,l,r;
		scanf("%d %d %d",&num,&l,&r);
		if(num==1){
			xiugai(l,r);
		}
		else{
			printf("%lld\n",chaxun(r)-chaxun(l-1));
		}
	}
	return 0; 
}  

知识点:树状数组

树状数组简介

树状数组是一种解决动态区间和的数组。
它的查询修改时间复杂度严格小于等于O(nlogn)。
它的操作用到了lowbit函数,它求的是这个数的二进制最右边的1所对应的数值。
通过观察下标二进制可知,lowbit(下标)为C[下标]可以分解成的数组元素个数,
如:C[8]=C[7]+C[6]+C[4]+A[8];

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

树状数组结构

这是树状数组的示意图(A是原数组,C是树状数组)
在这里插入图片描述
我们可以得出:
C[ 1 ] = A[ 1 ]
C[ 2 ] = A[ 2 ] + C [ 1 ]
C[ 3 ] = A[ 3 ]
C[ 4 ] = A[ 4 ] + C [ 3 ] + C [ 2 ]
C[ 5 ] = A[ 5 ]
C[ 6 ] = A[ 6 ] + C [ 5 ]
C[ 7 ] = A[ 7 ]
C[ 8 ] = A[ 8 ] + C [ 7 ] + C [ 6 ] + C [ 4 ] = A[ 1 ] + A[ 2 ] + A[ 3 ] + A[ 4 ] + A[ 5 ] + A[ 6 ] + A[ 7 ] + A[ 8 ]

树状数组实现

修改函数

void xiugai(int idx,int num){
	for(int i=idx;i<=n;i+=lowbit(i)){
		c[i]+=num; 
	}
}

假设我们要给a[1]增加3
我们要修改c[1( 000 1 2 0001_2 00012)],c[2( 001 0 2 0010_2 00102)],c[4( 010 0 2 0100_2 01002)],c[8( 100 0 2 1000_2 10002)]。
通过二进制可以看出:
从1( 000 1 2 0001_2 00012)到2( 001 0 2 0010_2 00102)是加了lowbit(1)=1。
从2( 001 0 2 0010_2 00102)到4( 010 0 2 0100_2 01002)是加了lowbit(2)=2。
从4( 010 0 2 0100_2 01002)到8( 100 0 2 1000_2 10002)是加了lowbit(4)=4。
由此可得上述代码。
编写注意事项:num为a[idx]增加的值

查询函数

long long chaxun(int r){
	long long sum=0;
	for(int i=r;i>0;i-=lowbit(i)){
		sum+=c[i];
	}	
	return sum;
}

假设要求从头到a[7]的前缀和。
要求c[7],c[6],c[4]之和。
通过二进制可以看出:
从7( 011 1 2 0111_2 01112)到6( 011 0 2 0110_2 01102)是减了lowbit(7)=1。
从6( 011 0 2 0110_2 01102)到4( 010 0 2 0100_2 01002)是减了lowbit(6)=2。
由此可得上述代码。
编写注意事项:该函数只能查询前缀和

树状数组进阶用法

区间增值单点查询

例题题面:BIT-2

给定长度为n( 1 ≤ n ≤ 1 0 6 1≤n≤10^6 1n106​​)的数组 a 1 , a 2 . . . a n a_1,a_2...a_n a1,a2...an( 1 ≤ a i ≤ 1 0 6 1≤a_i≤10^6 1ai106​​),进行q( 1 ≤ n ≤ 1 0 6 1≤n≤10^6 1n106​​)次操作,操作有两种:
1 l r k:将 a l a_l al~ a r a_r ar每个数都加上 k;( 1 ≤ ∣ k ∣ ≤ 1 0 6 1≤|k|≤10^6 1k106 1 ≤ l , r ≤ n 1≤l,r≤n 1l,rn​​)
2 k:输出 a k a_k ak( 1 ≤ k ≤ n 1≤k≤n 1kn​​)

实现思路

快速区间增减值,让我们想起了差分
差分是前缀和的逆运算
因为差分数组c的c[i]=a[i]-a[i-1]
所以给c[i]+100就相当于给a[i]~a[n]都加了100。
假设把区间[2,4]加10,求a[3]。
就可以把c[2]+10,c[5]-10,再利用树状数组求1~3前缀和。

AC代码

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e6+5;
long long n,a[N],c[N],maxx=0,q;
long long lowbit(long long x){
	return x&-x;
}
void xiugai(int l,int r,int x){
	for(int i=l;i<=n;i+=lowbit(i)){
		c[i]+=x;
	}
	for(int i=r+1;i<=n;i+=lowbit(i)){
		c[i]-=x;
	}
}
long long chaxun(int x){
	long long ans=0;
	for(int i=x;i>0;i-=lowbit(i)){
		ans+=c[i];
	}
	return ans;
}
int main(){
	scanf("%d %d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		xiugai(i,i,a[i]);
	}
	for(int i=1;i<=q;i++){
		int f;
		scanf("%d",&f);
		if(f==1){
			int l,r,k;
			scanf("%d %d %d",&l,&r,&k);
			xiugai(l,r,k);
		}
		else{
			int x;
			scanf("%d",&x);
			printf("%lld\n",chaxun(x));
		}
	}
	return 0;
}

求动态区间最大值

例题题面

给定 N 个数,求这 N 个数的最长上升子序列的长度,就是求出一段不断严格上升的部分,它不一定要连续。

实现思路

直接把求和改成求最大值,因为这道题中只是用他求前面最优解,在新的长度添加前求出就不用考虑修改前值的影响。

AC代码

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e5+5,M=1e6+5;
long long n,a[N],c[M],maxx=0;
long long lowbit(long long x){
	return x&-x;
}
void xiugai(long long x,long long y){
	for(int i=x;i<=maxx;i+=lowbit(i)){
		c[i]=max(c[i],y);
	}
}
long long chaxun(long long x){
	long long ans=0;
	for(long long i=x;i>0;i-=lowbit(i)){
		ans=max(c[i],ans);
	}
	return ans;
}
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=chaxun(a[i]-1);
		xiugai(a[i],ans+1);
	}
	printf("%lld",chaxun(maxx));
	return 0;
}

总结

这次我主要介绍了以下内容:

  1. 树状数组的实现
  2. 树状数组结构
  3. 题目:BIT-1
2023.11.11

我又添加了一些树状数组的进阶用法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值