28.线段树与树状数组基础

一、线段树

1.区间问题

线段树是一种在算法竞赛中常用来维护区间的数据结构。它思想非常简单,就是借助二叉树的结构进行分治,但它的功能却非常强大,因此在很多类型的题目中都有它的变种,很多题目都需要以线段树为基础进行发展。

具体来讲,线段树可以在 O ( log ⁡ N ) O(\log N) O(logN) 的时间复杂度内实现单点修改和区间修改,以及动态区间查询、求和、求最大、求区间最小值等操作。

2.基本结构

通常我们会将线段树构建成二叉树的样子,二叉树的每一个结点都表示一段区间,并使用数组来进行简化表示。每一个非叶子结点都有左右两棵子树,分别表示区间的左右两部分。现以根节点在数组中的下标为 1 1 1,则线段树具有以下的性质。

  • 一个结点若其在数组中的下标为 p o s pos pos,则它的左右儿子的下标分别为 2 p o s , 2 p o s + 1 2pos,2pos+1 2pos,2pos+1
  • 一个结点表示的区间为 [ l , r ] [l,r] [l,r],则它的左右儿子的表示的区间分别是 [ l , m i d ] , [ m i d + 1 , r ] [l,mid],[mid+1,r] [l,mid],[mid+1,r],其中 m i d = ( l + r ) / 2 mid=(l+r)/2 mid=(l+r)/2
  • 线段树的空间一般要开到 4 n 4n 4n,以防止特殊的越界发生。

例如,以结点总数 n = 10 n=10 n=10 为例构造的线段树如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.线段树的建立

下面以维护区间最小值为例来讲解线段树的基本操作:

  • 函数包含三个参数,分别表示该结点的下标、左端点、右端点。
  • 如果左端点等于右端点,则说明已经到叶子结点了,它的最小值就是它自己,直接对其赋值并返回。
  • 如果不是叶子结点,那么区间 [ l , r ] [l,r] [l,r] 的最小值就是区间 [ l , m i d ] , [ m i d + 1 , r ] [l,mid],[mid+1,r] [l,mid],[mid+1,r] 的最小值的最小值,所以应该递归地先求出子区间的最小值,再最后得到当前的最小值。
ll tree[4*maxn],a[maxn];
void build(ll pos,ll l,ll r)
{if(l==r){tree[l]=a[pos];return;}ll mid=(l+r)>>1;build(pos<<1,l,mid);build(pos<<1|1,mid+1,r);tree[pos]=min(tree[pos<<1],tree[pos<<1|1]);
}

建树的时间复杂度为 O ( n ) O(n) O(n)

4.单点更新

如果这个时候,某一个结点的值被更新了,那么可以考虑这个结点影响了哪些结点,如此只需要在 O ( log ⁡ n ) O(\log n) O(logn) 的时间就可以完成整棵树的更新了,而不需要花费 O ( n ) O(n) O(n) 的时间去重新建树。

思路很简单,我们已知被更新结点的下标,所以只需要判断它在左边还是右边即可,这样每次只选择一边,时间复杂度大大降低。但一定要记住,这里的更新和建树一样,是自下而上更新的,所以结点的值应该在递归的时候更新。

void update(ll pos,ll l,ll r,ll x,ll num)
{if(l==r){tree[pos]=num;return;}ll mid=(l+r)>>1;if(x<=mid)update(pos<<1,l,mid,x,num);elseupdate(pos<<1|1,mid+1,r,x,num);tree[pos]=min(tree[pos<<1],tree[pos<<1|1]);
}

5.单点查询

和单点更新几乎一致

void query(ll pos,ll l,ll r,ll x)
{if(l==r)return tree[pos];ll mid=(l+r)>>1;if(x<=mid)return query(pos<<1,l,mid,x);elsereturn query(pos<<1|1,mid+1,r,x);
}

6.区间查询

因为线段树上的区间划分是固定的,很多时候查询不可能刚好是某一个结点,所以我们需要对区间进行分段,最后依靠递归得到答案。

设我们要查询的区间为 [ s , e ] [s,e] [s,e],则到一个结点时可能有三种情况:

  • 若该结点是 [ s , e ] [s,e] [s,e] 的子区间,那么直接返回这个最小值
  • 若该结点的左半区间与 [ s , e ] [s,e] [s,e] 有交集,即存在一段 [ s , x ] [s,x] [s,x] [ l , m i d ] [l,mid] [l,mid] 有交集,那么只需要满足 s < = m i d s<=mid s<=mid 即可,随后查询左侧的最小值
  • 同理,若该结点的右半区间与 [ s , e ] [s,e] [s,e] 有交集,即存在一段 [ x , e ] [x,e] [x,e] [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 有交集,那么只需要满足 e > m i d e>mid e>mid 即可,随后查询右侧的最小值
  • 最后将左右两侧的最小值取最小值,就是当前区间的最小值
void query(ll pos,ll l,ll r,ll s,ll e)
{if(s<=l && r>=e)return tree[pos];ll mid=(l+r)>>1,ans=inf;if(s<=mid)ans=min(ans,query(pos<<1,l,mid,s,e));if(e>mid)ans=min(ans,query(pos<<1|1,mid+1,r,s,e));return ans;
}

7.区间更新

假设此时,修改的不只是一个元素的值,比如将某一个区间内的所有值都加上 k k k,这个时候就需要区间更新了。如果一个一个更新,无疑是非常糟糕的,还不如重建树。

所以我们可以借助区间查询的思想来进行。但这里有一个问题,区间更新与查询不同,这是会影响到某一整棵子树的值,难道我们要每次都更新到叶子结点吗?

为了优化这一过程,我们引入一个新的数组:懒惰标记。它被定义为当前区间所经历的且还没有向下传递的更新。用以累计这个区间所进行的改变,在需要的时候才向下传递给子结点进行更新。

ll lazy[4*maxn];
void down(ll pos)
{if(lazy[pos]){lazy[pos<<1]+=lazy[pos];lazy[pos<<1|1]+=lazy[pos];tree[pos<<1]+=lazy[pos];tree[pos<<1|1]+=lazy[pos];lazy[pos]=0;}
}
void update(ll pos,ll l,ll r,ll s,ll e,ll k)
{if(s<=l && r<=e){lazy[pos]+=k;tree[pos]+=k;return;}down(pos);ll mid=(l+r)>>1;if(s<=mid)update(pos<<1,l,mid,s,e,k);if(e>mid)update(pos<<1|1,mid+1,r,s,e,k);tree[pos]=min(tree[pos<<1],tree[pos<<1|1]);
}

二、树状数组

三、作业

1.黄题

P3372 【模板】线段树 1

P3870 [TJOI2009] 开关

P1816 忠诚

P1531 I Hate It

P5057 [CQOI2006] 简单题

2.绿题

P3373 【模板】线段树 2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/238546.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot结合easyexcel处理Excel文件

原创/朱季谦 假如有这样一个需求&#xff0c;每天需要读取以下表头的Excel文件&#xff0c;统计文件里击中黑名单的比例&#xff0c;该文件is_blacklist列的1表示击中了黑名单&#xff0c;0表示未击中黑名单。 基于该需求&#xff0c;可以在定时任务通过easyexcel工具进行处理…

通过adb命令查看当前界面的Activity

1、先进入shell 2、输入如下命令 dumpsys activity | grep "mFoc"执行效果如下&#xff1a; 从上图可以看到当前正在运行app的进程名称和当前显示的Activity完整路径类名。

Spring AOP 代码案例

目录 AOP组成 通知的具体方法类型 引入Spring AOP依赖 定义AOP层 UserController Postman测试 AOP工作流程 AOP组成 切面 : 切⾯&#xff08;Aspect&#xff09;由切点&#xff08;Pointcut&#xff09;和通知&#xff08;Advice&#xff09;组成&#xff0c;它既包含了…

第九节HarmonyOS 常用基础组件-Text

一、组件介绍 组件&#xff08;Component&#xff09;是界面搭建与显示的最小单位&#xff0c;HarmonyOS ArkUI声名式为开发者提供了丰富多样的UI组件&#xff0c;我们可以使用这些组件轻松的编写出更加丰富、漂亮的界面。 组件根据功能可以分为以下五大类&#xff1a;基础组件…

DynamicDataSource

DynamicDataSource 多数据源&#xff0c;读写分离&#xff0c;主从数据库

单片机实验(三)

前言 实验一&#xff1a;利用定时器T1的中断控制P1.7引脚输出音频信号&#xff0c;启动蜂鸣器发出一段熟悉的与众不同的具有10个音节的音乐音频。 实验二&#xff1a;使用定时器/计数器来实现一个LCD显示年、月、日、星期 、时、分、秒的电子表&#xff0c;要求时和分可以方便…

震坤行:数字驱动食品农副行业采购的新兴趋势与实践

震坤行&#xff1a;数字驱动食品农副行业采购的新兴趋势与实践 近年来消费者对于营养价值和健康的追求日益凸显&#xff0c;促使各类有机食品、低糖低脂食品、素食等健康食品受到热烈追捧。同时&#xff0c;以往单一的产品也被各家企业“卷”出了个性化&#xff0c;光是卖水&a…

vue实现数字千分位格式化 如6,383,993,037,937.463

1.封装文件&#xff1a;numberToCurrency.js /**实现数字千分位格式化 如6,383,993,037,937.463 */ export function numberToCurrencyNo(value) {if (!value) return 0// 获取整数部分const intPart Math.trunc(value)// 整数部分处理&#xff0c;增加,const intPartFormat …

WSL2+tensorflow-gpu 2.3.0 C++ 源码编译

wsl2已有gcc 版本为9.4.0&#xff0c;但tensorflow2.3.0需对应gcc7.3.1 tensorflow与cuda cudnn python bazel gcc版本对应关系 故需下载一个低版本的gcc&#xff0c;但同时还想保留较高版本的gcc&#xff0c;那么参考文章&#xff1a;深度学习环境搭建(二): Ubuntu不同版本g…

附录A 指令集基本原理

1. 引言 本书主要关注指令集体系结构4个主题&#xff1a; 1. 提出对指令集进行分类的方法&#xff0c;并对各种方法的优缺点进行定性评估&#xff1b; 2. 提出并分析一些在很大程度上独立于特定指令集的指令集评估数据。 3. 讨论语言与编译器议题以及…

微软 Power Platform 零基础 Power Pages 网页搭建教程学习实践(一)

微软 Power Platform 零基础 Power Pages 网页搭建教程学习实践 Power Pages 网页搭建 微软 Power Platform 零基础 Power Pages 网页搭建教程学习实践1、Power Pages 介绍2、开始创建一个站点3、选择一个合适的模板4、编辑我们的模板5、面向专业开发人员的高级开发功能6、预览…

完美的输出打印 SQL 及执行时长[MyBatis-Plus系列]

导读 Hi,大家好,我是悟纤。过着爱谁谁的生活,活出不设限的人生。 在我们日常开发工作当中,避免不了查看当前程序所执行的SQL语句,以及了解它的执行时间,方便分析是否出现了慢SQL问题。 MyBatis-Plus提供了两种SQL分析打印的方式,用于输出每条SQL语句及其执行时间,针…