树状数组详解与应用领域 c++ --二次元的programmer的博客

本文介绍了树状数组的概念、跳跃式扫描的原理及lowbit函数,探讨了树状数组在单点修改、区间查询以及区间修改、单点查询等问题中的应用,并给出了相关操作的C++代码实现。

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

这是本蒟蒻的第一篇博客,如有不妥,请各位大佬加以指正。

树状数组是什么?

学树状数组首先当然要知道树状数组是什么。下面是我粘过来的定义:
树状数组的定义
树状数组的查询和修改的时间复杂度都是log(n),空间复杂度则为O(n),这是因为树状数组通过将线性结构转化成树状结构,从而利用位运算进行跳跃式扫描。通常使用在高效的计算数列的前缀和,区间和。
(其实你只需要知道它的时间空间复杂度就行了,应用领域后文会讲)

跳跃式扫描的实现原理

在这里插入图片描述
小东西长得真别致
如上图:我们可以把C数组当成管理者,把A数组当成员工。A数组存的是原始数据,C数组存的是它指向的所有元素的和。一个管理者至少管理一名员工。
经过细心观察(鬼才能看出来。。。),我们可以发现管理者的编号的二进制与他管理员工的数目有着一些 微妙 的关系。

管理者编号编号的二进制管理员工数目
100011
200102
300111
401004
.。。。。。。。。。
810008

在这里插入图片描述
你可能会疑惑:有什么鬼关系?看起来二者毫无关系。但是,观察二进制那一栏,你会发现把从右往左数遇到第一个1的途中经过的0的个数作为2的次幂恰好是管理员工的个数。

拿3号管理者为例:它的二进制数从右往左数遇到第一个1所经过的0的个数为0,2的0次幂是1,所以3号管理者管一名员工。又如,8号管理者的二进制数从右往左数遇到第一个1所经过的0的个数为3,2的3次幂是8,所以8号管理者管八名员工。知道了这个,我们就能很方便的实现跳跃搜索。

跳跃式搜索的关键:lowbit函数

我们知道了管理者编号与管理员工数目之间的关系,下面我们要用代码来实现。
创建一个lowbit(),函数括号里面是管理者编号,返回值是管理员工数目。具体实现如下。

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

这段代码很简单,记住就行,并不用深究其原理。(我也不清楚)
有了lowbit,我们就可以轻松的利用树状数组进行各种骚操作了。

树状数组的应用领域

下面我会对单点修改,区间查询(对数组中某个值进行修改,查询某一段区间的和),区间修改,单点或区间查询(对数组中某一段区间进行修改,查询某个值的和或某一个区间的和)三大类问题进行说明与解决。

单点修改,区间查询

题目描述
如题,已知一个数列,你需要进行下面两种操作:

1.将某一个数加上x

2.求出某区间每一个数的和

输入输出格式
输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。

第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

接下来M行每行包含3个整数,表示一个操作,具体如下:

操作1: 格式:1 x k 含义:将第x个数加上k

操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和

输出格式:
输出包含若干行整数,即为所有操作2的结果。
输入样例:
5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
输出样例:
14
16

来源:洛谷。
题目链接
https://www.luogu.org/problemnew/show/P3374



如果不用树状数组,这道题直接暴力从头到尾扫一遍就行了。但是每一次修改和查询的时间复杂度都是O(n),如果有很多次询问,那么耗费的时间是无法忍受的。但是用树状数组的话,情况就会大大不一样。
(我把图又粘了过来,方便看)
在这里插入图片描述
首先我们来讨论修改单点的值的做法,也就是修改员工的值。因为管理者的值是他管理的所有员工的值的和,所以如果修改员工的值的话,那么所有管理此名员工的管理者的值都要修改。举个栗子:如果把一号员工的值加7,那么首先1号管理者的值要加7,通过lowbit发现1号管理者管1个,下一个修改1+1=2号管理者的值。2号管理者管2个,下一个修改2+2=4号管理者。一直修改到结尾。代码如下:

void add(int k,int h)
{
	while(k<=n) //修改到结尾
	{
		c[k]+=h; //相关的管理者的值要修改
		k+=lowbit(k); //寻找下一个相关管理者的位置
	}
}

而区间求和则要更简单。假设我们要求前6个员工的值的和,我们先调出6号管理者,他的值是5号和6号员工的值的和,把他加上。之后我们通过lowbit函数发现他管2名员工,于是接着我们调出4号管理者,他管从1到4号员工,再把它加上。这时整个员工数组就已经遍历完了,而我们也就求出前6个员工的和,也就顺利完成任务了。代码如下

int sum(int k)
{
	int ans=0;
	while(k>=1)
	{
		ans+=c[k];
		k-=lowbit(k);
	}
	return ans;
}

这就是一个简单的树状数组模板题,后文会有应用题。代码我贴下面了。

#include <bits/stdc++.h>
using namespace std;
int m,n;
int c[500000+100];
int lowbit(int x)
{
	return x & -x;
}
void add(int k,int h)
{
	while(k<=n)
	{
		c[k]+=h; 
		k+=lowbit(k);
	}
}
int sum(int k)
{
	int ans=0;
	while(k>=1)
	{
		ans+=c[k];
		k-=lowbit(k);
	}
	return ans;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int l;
		cin>>l;
		add(i,l); //我没有定义a数组,而是直接将a数组的值添加到c数组中。
	}
	for(int i=1;i<=m;i++)
	{
		int x,a,b;
		cin>>x>>a>>b;
		if(x==1) add(a,b);
		else cout<<sum(b)-sum(a-1)<<endl;
	} 
} 

区间修改,单点查询

这个要用到差分数组,我把详解地址贴上,这里就简单叙述一下。
https://www.cnblogs.com/kickit/p/9172189.html
举个栗子:

原数组1529-3
差分数组14-37-12
差分数组的前缀和1529-3

我们可以发现差分数组某一位的前缀和就是这一位的原数组的值。因此我们可以利用差分数组的前缀和来求某点的值,完成了单点查询的要求。
而区间修改不需详细了解其原理,记住就行。如果我要在原数组2到4的区间上加5(数组下标从1开始,对不起,我的习惯)。我只需在差分数组的第2位加上5,第4位的后一位(第5位)减去5就行。过程如下。

原数组1529-3
差分数组14-37-12
差分数组的前缀和1529-3
修改后数组110714-3
修改后差分数组19-37-17
修改后差分数组前缀和110714-3

仔细观察上表,跟着理解一下,记住就行了。上题!!!:


题目描述

如题,已知一个数列,你需要进行下面两种操作:

1.将某区间每一个数数加上x

2.求出某一个数的值

输入输出格式

输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。

第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

接下来M行每行包含2或4个整数,表示一个操作,具体如下:

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k

操作2: 格式:2 x 含义:输出第x个数的值

输出格式:
输出包含若干行整数,即为所有操作2的结果。

输入输出样例

输入样例:
5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4

输出样例:
6
10
题目链接:https://www.luogu.org/problemnew/show/P3368


上代码!!!

#include <bits/stdc++.h>
#define MAX 100050
using namespace std;
typedef long long ll;
int m,n;
int a[MAX],c[MAX];
int lowbit(int x)
{
	return x&-x;
}
void add(int t,int da)
{
	while(t<=n)
	{
		c[t]+=da;
		t+=lowbit(t);
	}
}
ll ask(int t)
{
	ll sum=0;
	while(t>0)
	{
		sum+=c[t];
		t-=lowbit(t);
	}
	return sum;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		add(i,a[i]-a[i-1]);
	}
	cin>>m;
	for(int i=1;i<=m;i++)
	{
		char st[10];
		scanf("%s",st);
		int aa,bb,cc;
		if(st[0]=='Q')
		{
			cin>>aa;
			cout<<ask(aa)<<endl;
		}else
		{
			cin>>aa>>bb>>cc;
			add(aa,cc);
			add(bb+1,-cc);	
		}
	}
	return 0;
} 

区间修改,区间查询的情况可以结合前面讲的两大类。树状数组简单的应用就是这些,更加复杂的用法需要多加练习,熟能生巧。
好啦,这篇博客就到这里了,顺便安利一篇维护区间最大值的树状数组方法。
https://blog.csdn.net/zhouzi2018/article/details/81108940
如果这篇博客有错误之处,请尽快联系博主,或留言,谢谢各位大佬!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arodex

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值