首页 > 其他分享 >树状数组

树状数组

时间:2024-07-19 20:41:30浏览次数:9  
标签:单点 树状 int lowbit 线段 数组

什么是树状数组

顾名思义就是一个结构为树形结构的数组,于二叉树的结构类似但又不同,它是在二叉树的结构上删除了一些中间节点,来看两幅图就明白了.

1.这是二叉树的结构

 


2.这是树状数组的结构

 

不难发现,树状数组相比于二叉树删除了一些节点,但是为什么要删除呢?这就和树状数组的一些性质(lowbit)有关了,不懂没关系,继续往下看.

 

树状数组可以解决什么问题呢?

可以解决大部分区间上面的修改以及查询的问题,例如1.单点修改,单点查询,2.区间修改,单点查询,3.区间查询,区间修改,换言之,线段树能解决的问题,树状数组大部分也可以,但是并不一定都能解决,因为线段树的扩展性比树状数组要强.

 

树状数组和线段树的区别在哪?

有人会问了既然线段树的问题能够用树状数组解决而且线段树还比树状数组扩展性强,那为什么不直接用线段树呢?问的很好,树状数组的作用就是为了简化线段树,举个例子:一个问题可以用线段树解决写代码半个小时,但是用树状数组只需要10分钟,那么你会选择哪一个算法呢?没错,基于某些简单的问题,我们没必要用到功能性强但实现复杂的线段树(杀鸡焉用宰牛刀).

 

树状数组的优点

优点:修改和查询操作复杂度于线段树一样都是logN,但是常数比线段树小,并且实现比线段树简单

缺点:扩展性弱,线段树能解决的问题,树状数组不一定能解决.

 

树状数组讲解

前置知识—lowbit(x)运算

 

问题引入

 

显然,我们一开始会想到暴力的朴素做法,单点修改操作时间复杂度O(1),区间求和,暴力遍历区间每一个数再相加时间复杂度O(n),如果区间求和查询的次数为n次,那么中的时间复杂度为O(  n^2  )

对于大数据的题来说肯定会TLE,此时如果用树状数组的话复杂度可以降到O(nlogn).

树状数组结构分析

接下来分析树状数组的原理

上面是树状数组的结构图,t[x]保存以x为根的子数中叶子节点值的和,原数组为a[]

t[1]=a[1],     t[2]=t[1]+a[2],     t[3]=a[3] ........ 这样t[8]就是a[1]~a[8]的和了
那么原数组前4项的和  t[4]  =    t[2]+t[3]+a[4]     =    t[1]+a[2]+t[3]+a[4]    =    a[1]+a[2]+a[3]+a[4],   看似没有什么特点,别着急往下看

 

 

 我们通过观察节点的二进制数,进一步发现,树状数组中节点x的父节点为x+lowbit(x),例如t[2]的父节点为t[4]=t[2+lowbit(2)]

 

关于修改操作都是 i+=lowbit[i],查询操作的都是 i-lowbit[i]

单点修改,区间查询

所以我们在单点修改的同时,更新父节点就变得尤为简单,,例如我们对a[1]+k,那么祖先节点t[1],t[2],t[4],t[8]都需要+k更新(因为t[]表示前缀和),此时我们就可以用lowbit操作实现.

int add_dandian(int x,int k)
{
	for(int i=x;i<=n;i+=lowbit(i))
	t[i]+=k;
}

那么单点修改实现了,如何实现区间查询呢?
例如:我们需要查询前7项的区间和sum[7]

 通过图中不难看出,sum[7]=t[7]+t[6]+t[4] ,我们进一步发现,6=7-lowbit(7),4=6-lowbit(6),所以我们可以通过不断的-lowbit操作来实现求和

int ask(x){
	int sum = 0;
	for(int i=x;i;i-=lowbit(i)){
		sum+=t[i];
	}
	return sum;
}

int search(int L,int R)
{
	int ans = 0;
	for(int i=L-1;i;i-=lowbit(i))
	ans-=c[i];
	for(int i=R;i;i-=lowbit(i))
	ans+=c[i];
	return 0;
}

  

区间修改,单点查询

对于这一类操作,我们需要构造出原数组的差分数组c,然后用树状数组维护c数组即可

对于区间修改的话,我们只需要对差分数组进行操作即可,例如对区间[L,R]+k,那么我们只需要更新差分数组add(L,k),add(R+1,-k),这是差分数组的性质.

int update(int pos,int k)//pos表示修改点的位置,K表示修改的值也即+K操作
{
	for(int i=pos;i<=n;i+=lowbit(i))
	c[i]+=k;
	return 0;
}
update(L,k);
update(R+1,-k);

对于单点查询操作,求出c数组的前缀和即可,因为a[x]=差分数组c[1]+c[2]+…+c[x]的前缀和,这是差分数组的性质之一.

ll ask(int pos)//返回区间pos到1的总和
{
	ll ans=0;
	for(int i=pos;i;i-=lowbit(i)) ans+=c[i];
	return ans;
} 

  

区间修改,区间查询

这一类操作使用树状数组就显得及其复杂,这时候我们建议使用扩展性更强的线段树来解决,在此就不进行树状数组的讲解了.

 

树状数组题目练习

下面2到题都是模板题,不需要经行讲解,学会了上面的树状数组知识就可以AC

树状数组1

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;

int n, m, a[N], op, x, y, k, s[N];
int lowbit(int x)
{
	return x&(-x);
} 
void add(int x, int k)  
{
	for (int i=x; i<=n; i+=lowbit(i)) s[i]+=k;
}
int ask(int x, int y)
{
	int res=0;
	for (int i=y; i>=1; i-=lowbit(i)) res+=s[i];
	for (int i=x-1; i>=1; i-=lowbit(i)) res-=s[i];
	
	return res;
}
int main()
{
	scanf("%d%d", &n, &m);
	for (int i=1; i<=n; i++) 
	{
		scanf("%d", &a[i]);
		add(i, a[i]);
	}
	
	while (m--)
	{
		scanf("%d", &op);
		if (op==1)
		{
			scanf("%d%d", &x, &k);
			add(x, k);
		}
		else if (op==2)
		{
			scanf("%d%d", &x, &y);
			printf("%d\n", ask(x, y));
		}
	}
	return 0;
}

  

 

树状数组2

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;

int n, m, a[N], op, x, y, k, s[N];
int lowbit(int x)
{
	return x&(-x);
} 
void add(int x, int k)  
{
	for (int i=x; i<=n; i+=lowbit(i)) s[i]+=k;
}
int ask(int x)
{
	int res=0;
	for (int i=x; i>=1; i-=lowbit(i)) res+=s[i];
	
	return res;
}
int main()
{
	scanf("%d%d", &n, &m);
	for (int i=1; i<=n; i++) 
	{
		scanf("%d", &a[i]);
		add(i, a[i]-a[i-1]);
	}
	
	while (m--)
	{
		scanf("%d", &op);
		if (op==1)
		{
			scanf("%d%d%d", &x, &y, &k);
			add(x, k);
			add(y+1, -k);
		}
		else if (op==2)
		{
			scanf("%d", &x);
			printf("%d\n", ask(x));
		}
	}
	return 0;
}

  

 

标签:单点,树状,int,lowbit,线段,数组
From: https://www.cnblogs.com/didiao233/p/18312322

相关文章

  • java数组之数组工具类——Arrays的使用
    一、Arrays工具类简述    在java的类库中有许多现成的已经封装好的方法,可以供开发人员使用,比如我们之前学的二分法查找或者快速排序等。所以在实际的开发中,我们是不用自己编写这些常用的方法的。那么在常用的数组方法在哪里的?java作为面向对象的语言,所有方法都会封装......
  • C语言_数组(1)
    一、一维数组1、数组的创建方式:数组是有限个相同类型数据元素的集合。type_t arr_name [const_n];//type_t是指数组的元素类型//const_n是一个常量表达式,用来指定数组的大小创建例子:注意:数组在创建时,[]里的必须是常量。但是如果编译器支持C99标准的话可以使用......
  • 合并排序数组
    合并排序数组(蓝桥杯题库)题目描述给定排序数组A和B,实现一个算法将B按排序顺序合并到A中。介绍如下:数组A和B的均为排序数组,数字按从小到大排列。数组A的的长度为 ......
  • 34.拷贝数组
    定义一个方法:copyOfRange(int[]arr,intx,inty)将数组arr中从索引x开始(包含x)到索引y结束(不包含y)中的元素,复制到新数组中,并将新数组返回例:原始数组arr={1,2,3,4,5,6,7,8,9},新数组newArr={4,5,6,7}publicstaticvoidmain(String[]args){//1.静态初始化定......
  • C++数组中lower_bound和upper_bound函数的用法
    lower_bound函数首先,对于一个升序的数组(下标从0或者1开始是无所谓的,这里假设下标从1到n),即:a[1]<=a[2]<=a[3]<=...<=a[n]这个数列是(非严格)单调递增的。lower_bound(a+1,a+n+1,x)会返回a[1..n]中所有\(\gex\)的元素里面最小的那个数的地址。也就是说,......
  • 了解操作数组的方法
    1.join()Array.join()方法将数组中所有元素都转换为字符串并连接在一起,返回最后生成的字符串。可以指定一个可选的字符串在生成的字符串中来分隔数组的各个元素。如果不指定分隔符,默认使用逗号。例如:letarr=[1,2,3,4,5]//创建一个包含5个元素的数组console.l......
  • 数据结构与算法 数组篇之长度最小的子数组
    问题描述:给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl,numsl+1,...,numsr-1,numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。题目链接:.-力扣(LeetCode)解题思路:双指针,......
  • EXCEL:按有序列表对数组进行排序,无需自定义列表
    我有一张邮政编码表,其中包含发送到每个邮政编码的货件数量。我想按特定顺序按邮政编码对这个数组进行排序,我将其放在第二个列表中。我不想按客户数量或邮政编码的数字顺序排序,而是按这个专门排名的列表排序。我无法使用自定义排序功能,因为我的列表对于此功能来说太长了。......
  • 代码随想录算法训练营第30天 | 贪心算法 2: 122.买卖股票的最佳时机II、55. 跳跃游戏
    代码随想录算法训练营第30天|贪心算法2:122.买卖股票的最佳时机II、55.跳跃游戏、45.跳跃游戏II、1005.K次取反后最大化的数组和122.买卖股票的最佳时机IIhttps://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/代码随想录https://programmerca......
  • 解析json数组 , Java
    使用JSONObject1、需要解析的json串{"retCode":0,"retMSg":"成功","data":[{"name":"李雷","id":"001","score":{&q......