树状数组(详细分析+应用),看不懂打死我!

2023-05-16

树状数组介绍

在学习一个算法之前一定要清楚它能干嘛,能解决什么样的问题,对你解题是否有帮助,然后才去学习它!

那么接下来看如下几个问题

什么是树状数组

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

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

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

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

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

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

树状数组的优点

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

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

树状数组讲解

前置知识—lowbit(x)运算

如何计算一个非负整数n在二进制下的最低为1及其后面的0构成的数?
例如: 44 = ( 101100 ) 2 44=(101100)_2 44=(101100)2,最低为1和后面的0构成的数是 ( 100 ) 2 = 4 (100)_2=4 (100)2=4
所以 l o w b i t ( 44 ) = l o w b i t ( ( 101100 ) 2 ) = ( 100 ) 2 = 4 lowbit(44)=lowbit((101100)_2)=(100)_2=4 lowbit(44)=lowbit((101100)2)=(100)2=4,那么lowbit运算时怎么实现的呢?

44的二进制=(101100),我们对44的二进制数取反+1,也即~44+1,得到-44

-44的二进制=(010100),然后我们把44和-44的二进制进行按位与运算,也即按位&得到,二进制000100,也就是十进制的4

所以lowbit(x) = x&(-x)

问题引入

在这里插入图片描述
显然,我们一开始会想到暴力的朴素做法,单点修改操作时间复杂度O(1),区间求和,暴力遍历区间每一个数再相加时间复杂度O(n),如果区间求和查询的次数为n次,那么中的时间复杂度为O( n 2 n^2 n2),对于大数据的题来说肯定会T,此时如果用树状数组的话复杂度可以讲到O(nlogn).

树状数组结构分析

接下来分析树状数组的原理
在这里插入图片描述
上面时树状数组的结构图,t[x]保存以x为根的子数中叶子节点值的和,原数组为a[]
那么原数组前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)]

单点修改,区间查询

所以我们在单点修改的同时,更新父节点就变得尤为简单,,例如我们对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;
}

这只能求区间 [ 1 , x ] 的 区 间 和 , 那 么 如 何 求 [ L , R ] 的 区 间 和 呢 ? [1,x]的区间和,那么如何求[L,R]的区间和呢? [1,x][L,R],这时候利用前缀和相减的性质就可以了, [ L , R ] = [ 1 , R ] − [ 1 , L − 1 ] [L,R]=[1,R]-[1,L-1] [L,R]=[1,R][1,L1]
代码

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

区间修改,单点查询

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

对于区间修改的话,我们只需要对差分数组进行操作即可,例如对区间[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);

对于单点查询操作,求出b数组的前缀和即可,因为a[x]=差分数组b[1]+b[2]+…+b[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
AC代码

#include<iostream>
#define lowbit(x) (x&(-x))
typedef long long ll; 
using namespace std;
int c[2000006];
int n,m;
ll ans;
int add_dandian(int x,int k)
{
	for(int i=x;i<=n;i+=lowbit(i))
	c[i]+=k;
}
int search(int begin,int end)
{
	for(int i=end;i;i-=lowbit(i))
	ans+=c[i];
	for(int i=begin-1;i;i-=lowbit(i))
	ans-=c[i];
	return 0;
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int number;
		scanf("%d",&number);
		add_dandian(i,number);
	}
	for(int i=1;i<=m;i++)
	{
		int choice,x,y;
		scanf("%d %d %d",&choice,&x,&y);
		if(choice==1) add_dandian(x,y);
		else
		{
			ans=0;
			search(x,y);
			printf("%lld\n",ans);
		}
	}
	return 0;
}

树状数组2

#include<iostream>
#include<cstring>
#define lowbit(x) (x&(-x)) 
using namespace std;
typedef long long ll;
const int Maxn=1e6+5;
int a[500005];
int d[Maxn]={0};//d[i]的值,d[i]表示第i和i-1个数的差值
ll c[Maxn]; 
int n,m;
int update(int pos,int k)//pos表示修改点的位置,K表示修改的值也即+K操作
{
	for(int i=pos;i<=n;i+=lowbit(i))
	c[i]+=k;
	return 0;
}
ll ask_qujian(int pos)//返回区间pos到1的总和
{
	ll ans=0;
	for(int i=pos;i;i-=lowbit(i)) ans+=c[i];
	return ans;
} 
int main()
{
	memset(c,0,sizeof(c));
	scanf("%d %d",&n,&m);
	a[0]=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		d[i]=a[i]-a[i-1];
		update(i,d[i]);
	}
	for(int i=1;i<=m;i++)
	{
		int choice,x,y,k;
		scanf("%d",&choice);
		if(choice==1)
		{
			scanf("%d %d %d",&x,&y,&k);
			update(x,k);
			update(y+1,-k);
		}
		else
		{
			scanf("%d",&x);
			printf("%lld\n",ask_qujian(x));
		}
	}
	return 0;
}

树状数组求逆序对
分析:对于原序列,比当前位置数大的数前出现在序列中,就会构成逆序对,例如:5 3 2 1,5,3,2比1先出现且都比1大,那么此时就构成了3个逆序对数,那么我们可以对以及出现的数字进行标记,枚举序列中每一个位置的数,统计有多少比它大的数字以及出现,然后累加进答案即可,这就是一个单点修改+区间查询的操作,树状数组实现,不过需要注意的是这道题数字很大,需要离散化存储.

注意:这道题自定义排序参数cmp的实现,不能单纯的a.val<b.val,如果相等的话也要保证位置不变,不然贡献会增多,想想为什么?.
AC代码

#include<bits/stdc++.h>
using namespace std;
const int Maxn = 5e5+10;
int t[Maxn]={0};//树状数组 
typedef struct node{
   int val,ind;
}Node;
Node stu[Maxn];
int Rank[Maxn];
typedef long long ll;
int n; 
int lowbit(int x){return x&(-x);}
/*单点修改*/
void add(int pos){
	for(int i=pos;i<=n;i+=lowbit(i)) t[i]+=1;
}
/*区间求和*/
int ask(int pos){
	int ans = 0; 
	for(int i=pos;i;i-=lowbit(i)) ans+=t[i];
	return ans;
} 
/*不能单纯的a.val<b.val,如果相等的话也要保证位置不变,不然贡献会增多*/
int cmp(Node a,Node b){
	if(a.val==b.val)
	return a.ind<b.ind;
	
	return a.val<b.val;
}
int main()
{
	ll ans = 0;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>stu[i].val;
		stu[i].ind=i;
	}
	sort(stu+1,stu+n+1,cmp);
	/*离散化操作*/
	for(int i=1;i<=n;i++){
		Rank[stu[i].ind] = i;
	} 
	for(int i=1;i<=n;i++){
		int pos = Rank[i];

		ans+=ask(n)-ask(pos);//digit+1~n中有多少数字已经出现就贡献多少逆序对数,累加到答案 
			add(pos);//单点修改
	}
	cout<<ans;
	return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

树状数组(详细分析+应用),看不懂打死我! 的相关文章

  • 一款二进制文件查看器

    由于使用的是Notepad 43 43 64位版本 xff0c 在网上找了很多二进制查看插件HexEditor dll要么是32位不兼容 xff0c 要么是出现除零的错误 xff08 以前找到过一次支持Notepad 43 43 64位版本
  • YOLO目标检测

    一 背景 基于深度学习技术的视觉目标检测近年去的长足发展 但仍然有许多方面问题需要优化 二 YOLO算法的特点 YOLO作为一种性能优异的通用目标检测系统 xff0c 为了保证检测的效率 xff0c 提出one stage的思想 xff0c
  • (Qt中添加编译选项)QT在交叉编译时出现parameter passing for argument of type ‘std::_Rb_tree xxxxx changed in GCC 7.1

    QT版本都是5 1x 先是在Ubuntu机器上写的代码 xff0c GCC版本为5 4 xff0c 代码编译无 任何警告 后来移植到开发板 xff08 GCC版本为7 1 xff09 进行编译时 xff0c 提示这种警告 发生在代码中对st
  • C++按行读取文本并解析

    项目中需要按行读取文本文件 xff0c 并对每一行内容进行解析 每一行都是固定的字段数 xff0c 字段之间用空格隔开 span class token macro property span class token directive k
  • error C2447: “{”: 缺少函数标题(是否是老式的形式表?)

    error C2447 缺少函数标题 是否是老式的形式表 网上有人说 这个BUG是因为在win7上使用了 LF 的格式编码导致的 使用Notepad 43 43 修改成 BOM UTF8 和 windows 的 CR LF 格式一切正常 确
  • Visual Studio 2017 代码自动对齐

    点 编辑 高级 设置选定内容的格式 或者按Ctrl 43 K 然后再按Ctrl 43 F 就好了 你可以在常用快捷键自定义 窗口中进行查看 1 进入工具 选项 对话框 2 选择 环境 键盘 3 在 显示命令包含 下面的对话框中输入 对齐 关
  • CSDN 排版之颜色、字体、字号及背景色

    颜色 xff1a span class token operator lt span font color span class token operator 61 span blue span class token operator g
  • 使用QTCreator编程时,如何利用dmp文件定位程序奔溃

    写这篇文章之前 xff0c 看了一些其他人的博客 xff0c 但不是很详细 xff0c 缺这少那的 xff0c 好多都是复制粘贴别人的东西 自己动手弄了弄 xff0c 可以使用 xff0c 就记下来备忘与分享 前言 开发环境说明 编程IDE
  • Pytorch中transforms.RandomResizedCrop使用说明

    加载数据 训练集数据扩充 数据增强 和归一化 数据扩充 数据增强 的意义在于通过人为的随机范围裁剪 缩放 旋转等操作 增大训练集中的数据多样性 全面性 进而提高模型的泛化能力 训练集数据扩充和归一化 在验证集上仅需要归一化 data tra
  • C++中调用Python的办法

    1 背景 一直采用C 43 43 作为主语言开发 xff0c 最近遇到一个项目需要解析PDF文件中的文本内容 xff0c 直接采用C 43 43 来做显得不是很方便 xff0c 但用python来做就显得很简单了 难点在于如何C 43 43
  • Qt Creator远程调试嵌入式ARM开发板

    1 环境 Win10 64位系统上通过Virtual Box安装了一个Ubuntu虚拟机 ubuntu的版本 xff1a Linux kernel 4 15 0 142 generic 146 16 04 1 Ubuntu SMP Ubun
  • 套接字(描述符)读取指定的字节数

    检测fd句柄是否可读 xff0c ms毫秒超时 参数 xff1a df in 检测的句柄 ms in 超时 xff0c 毫秒 返回 xff1a 1 可读 xff0c 或者已经断开 0 超时 xff0c 仍然不可读 1 错误 int IsRe
  • 4.4线索二叉树遍历

    1 中序线索二叉树遍历 找到第一个中序遍历的结点 ThreadNode span class token operator span span class token function Firstnode span span class t
  • 自动根据本机字节序 将小端字节序的报文(字符数组)转为整数

    1 xff0c 判断本机的字节序 xff08 大端优先 小端优先 xff09 判断当前PC为大端还是小端字节序 64 返回值 xff1a 1 大端 xff1b 0 小端 int JudgeEndianOfPC int num 61 1 if
  • 智能指针的使用

    智能指针在C 43 43 11版本之后提供 xff0c 包含在头文件 lt memory gt 中 xff0c shared ptr unique ptr weak ptr 1 xff0c shared ptr的使用 shared ptr使
  • 图像检索系列——利用深度学习实现以图搜图

    转载自 xff1a 图像检索系列 利用深度学习实现以图搜图 知乎 前言 在上一篇文章 图像检索系列 利用 Python 检测图像相似度 中 xff0c 我们介绍了一个在图像检索领域非常常用的算法 感知哈希算法 这是一个很简单且快速的算法 x
  • Windows下select模型(以及EAGAIN、EWOULDBLOCK、EINTR)

    在这里记录一下 xff0c 以前都是新项目用到了就从旧项目中拷贝 自从将博客当作记事本 xff0c 发现自己多了一个好习惯 Windows下select模型 程序员攻略 CSDN博客 套接字IO超时设置和使用select实现超时管理 wj
  • RANSAC算法理解

    RANSAC是 RANdom SAmple Consensus xff08 随机抽样一致 xff09 的缩写 它可以从一组包含 局外点 的观测数据集中 xff0c 通过迭代方式估计数学模型的参数 它是一种不确定的算法 它有一定的概率得出一个
  • C++ 环形缓冲区(队列)简单实现

    1 说明 在实际工作中 xff0c 如果数据流量过大 xff0c 可以先把数据接收到数据缓冲区中 xff0c 处理之后再取出 我们定义的包协议可以采用定长包 xff0c 可以采用不定长度的包 xff0c 环形缓冲区都能处理 2 使用场景 2
  • Visual Studio Code (vscode) 配置 C / C++ 环境

    Visual Studio Code vscode 配置 C C 43 43 环境 步平凡 博客园 在电脑安装软件管控严格的情况下 xff0c 想装VS装不了 xff0c 就装轻量版的VSCode了 以上写得很好 xff0c 照做即可 本人

随机推荐

  • c++实现basename

    window API居然不包含Linux中很好用的basename函数 xff0c 实现了一下 xff0c 留个记录 xff0c 省得日后重复写 std string m basename std string fullPath size
  • tortoiseGit教程

    0 前言 TortoiseGit其实是一款开源的git的版本控制系统 xff0c 也叫海龟git TortoiseGit提供了人性化的图形化界面 xff0c 不用像Git一样输入许多语句 xff0c 像git init git add gi
  • 用STL库创建线程

    测试了3种方式 xff1a 1 xff1a 子线程不带返回值 2 xff1a 子线程带返回值 3 xff1a 子线程带引用类型参数 使用join方式 xff0c 让父线程等待子线程运行结束 TestTemp cpp 定义控制台应用程序的入口
  • 4.5树的存储

    双亲表示法 xff0c 孩子表示法 xff0c 孩子兄弟表示法 1 双亲表示法 查找双亲简单 空数据导致遍历更慢 xff0c 查指定节点的孩子只能遍历 span class token keyword typedef span ElemTy
  • Windows下MySQL数据库的安装、配置及C++使用案例

    1 安装及配置 Windows判断本地是否安装mysql以及mysql安装过程 企鹅要去银河思考人生 xff01 xff01 xff01 的博客 CSDN博客 windows查看是否安装mysql 注意按照文中提示 xff0c 配置好环境变
  • C++获取系统毫秒级时间(自1970年1月1日至今的毫秒数)

    跟系统时间相关的 ifdef WIN32 include lt time h gt include lt windows h gt else include lt sys time h gt endif unsigned long long
  • Window 10下SQL Server的安装配置以及C++使用案例

    1 SQL Server2008的安装与配置 参照下面这篇博客实现即可 里面提供了安装包下载方式 xff08 百度网盘有点慢 xff09 安装及配置步骤 SQLServer安装教程 xff08 史上最详细版本 xff09 杨林伟的博客 CS
  • 基于OpenCV实现的多角度、多目标模板匹配项目实战案例

    1 说明 本案例采用NCC的匹配 金字塔 为了加速 思想 基于OpenCV实现的多角度 多目标模板匹配 不支持尺度不变 若研究旋转 尺度不变性的匹配 请参考本人的OpenCV专栏内的 nbsp OpenCV实现多角度多尺度模板匹配 基于形状
  • 程序日志模块的两种模式

    程序员都知道程序的运行日志在不少时候都非常有用 xff0c 利于排查 理清逻辑 一般而言 xff0c 日志都按天生成 xff0c 并且具备自动清理多少天以前的旧日志 xff0c 避免无限增长占用磁盘 下图展示了2种日志模式 模式一 1 xf
  • C++开发面试常考

    C 43 43 后台开发面试常考 pudn com
  • 如何在微软官网上下载旧版本的visual studio

    想在微软官网下载旧版本的VS 太长不想看的可以直接戳网址进入最终的界面 xff1a Visual Studio 较旧的下载 2019 2017 2015 和以前的版本想从官网首页一步一步进入到最终下载界面的可以看下面详细步骤 xff1a 1
  • 基于OpenCV实现的RANSAC随机抽样一致性直线拟合

    概要 本文介绍基于ransac随机抽样一致性随机抽样一致性的直线拟合方法 涵盖一下的内容 ransac的算法思想 ransac的算法步骤 如何调整ransac算法迭代的次数 基于opencv编码实现 ransac算法流程 RANSACRAN
  • 基于OpenCV(C++)实现的RANSAC随机抽样一致性的曲线拟合(二次)

    nbsp 0 前言 nbsp nbsp 前不久整理了RANSAC直线拟合的文章 基于OpenCV实现的RANSAC随机抽样一致性直线拟合 thequitesunshine007的博客 CSDN博客 这篇文章与其类似 只是从拟合直线变为拟合曲
  • 最小二乘least-squares拟合曲线(三次或多次)

    1 说明 基于最小least squares去拟合出多次曲线 考虑到了所有的样本点 因此这种方法对噪声敏感 尤其是遇到较为突兀明显的噪声时 曲线的形状易受干扰 2 代码 代码细节仔细读基本都能读懂 或者查一下也不是什么大问题 include
  • 4.6树和森林的遍历

    一 树的遍历 1 先根遍历 对应二叉树先序遍历 span class token keyword void span span class token function PreOrder span span class token punc
  • 扩展欧几里得算法(简单易懂,详细分析)

    扩展欧几里得算法 扩展欧几里得算法证明 43 应用 61 61 欧几里得算法 61 61 61 61 扩展欧几里得算法 61 61 应用1 xff1a 求一元二次线性方程的整数解应用二 xff1a 求ax 61 c mod p 应用三 xf
  • 佩尔方程(超详细推导+例题讲解) 每日一遍,算法再见!

    这里写目录标题 佩尔方程第一类佩尔方程第一类佩尔方程例题讲解 第二类佩尔方程 佩尔方程 第一类佩尔方程 定义 xff1a 形如 x 2 d
  • FFT(傅里叶快速变换,详细讲解+推导) 每日一遍,算法再见!

    FFT详细推导 FFT 傅里叶快速变换 一 前置知识1 复数和单位根2 单位根的三个引理3 多项式 二 FFT 快速傅里叶变换推导 三 IFFT四 FFT求解多项式乘积模板代码1 递归版2 非递归版 这个更快 xff0c 省去了递归时间 五
  • 背包问题----分组背包(超详细讲解)

    树形dp 分组背包 43 依赖背包 分组背包1 定义2 讲解3 练习题 依赖背包1 定义2 讲解3 练习题 分组背包 1 定义 分组背包 xff0c 通俗的讲就是 xff0c 给你N组物品 xff0c 然后每一组你至多选择一个物品 也可以不
  • 树状数组(详细分析+应用),看不懂打死我!

    树状数组介绍 在学习一个算法之前一定要清楚它能干嘛 xff0c 能解决什么样的问题 xff0c 对你解题是否有帮助 xff0c 然后才去学习它 那么接下来看如下几个问题 什么是树状数组 顾名思义就是一个结构为树形结构的数组 xff0c 于二