超级用心的线段树介绍

2023-11-01 23:08
文章标签 介绍 超级 线段 用心

本文主要是介绍超级用心的线段树介绍,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

洛谷同步。

前:笔者觉得线段树很重要,听说我基友___X3___要写一篇关于线段树的博客,我就也写了一篇介绍详细易懂的博客。持续更新。

-----------------------咕咕的分界线------------------------------------------------

更新日志:

update 11.21/2020 :写成。

-----------------------咕咕的分界线------------------------------------------------

Part 1

线段树是一种基于分治思想的二叉树结构,较通用,适于解决区间问题。

线段树的每个节点代表一个区间,根节点唯一,表示 1 − n 1-n 1n

每个叶节点表示一个长度为1的元区间 [ x , x ] [x,x] [x,x]

对于每个节点(内部) [ l , r ] [l,r] [l,r],它的左节点是 [ l , m i d ] [l,mid] [l,mid],右子节点是 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]

m i d = ( l + r ) / 2 = ( l + r ) > > 1 mid=(l+r)/2=(l+r)>>1 mid=(l+r)/2=(l+r)>>1

m i d mid mid 向下取整)

-----------------------咕咕的分界线------------------------------------------------

性质:线段树除去最后一层,一定是一棵完全二叉树,所以,在理想情况下, n n n 个节点的线段树有
n + n / 2 + n / 4 + . . . + 2 + 1 = 2 n − 1 n+n/2+n/4+...+2+1=2n-1 n+n/2+n/4+...+2+1=2n1
个节点。

因此,数组长度要开到不小于4倍

我暴力开过100倍,不会MLE…

-----------------------咕咕的分界线------------------------------------------------

图片链接:(图老是挂,于是决定使用链接。)

注意,线段树的编号与堆的编号类似,相当于 d f s dfs dfs 序。

线段树图片

------------所以,第一部分圆满结束---------------------------------

Part 2,标记延迟

这就是大名鼎鼎臭名远扬 l a z y t a g lazytag lazytag

先说一下标记延迟的作用:将一次区间修改的时间复杂度从 O ( n ) O(n) O(n) 优化到 O ( l o g n ) O(log n) O(logn)

本文 l o g log log 默认为2。

如果在修改指令中,要修改节点 p p p 代表的区间 [ l , r ] [l,r] [l,r]。正常的修改方法就是访问以它为根节点的子树并修改。复杂度是 O ( n ) O(n) O(n),其中 n n n 为此子树节点数。

但是,我们没有用到这个区间作为候选答案。所以,更新 p p p 的整棵子树是浪费时间的徒劳行为。

怎么解决呢???

加入 l a z y t a g lazytag lazytag,懒标记。

ps:关于标记,还有一种叫做“标记永久化”的技巧。就是不下传标记,而是累计递归时产生的标记影响。这种做法局限性大,仅在二维线段树和可持久化线段树中有所应用。所以不介绍了。

懒标记的具体实现就是在回溯时向节点 p p p 增加一个标记,表示“该节点在过去被修改了,但是它的子节点尚未被更新”。

后续操作中,如果要从 p p p 往下递归,我们就再检查一下 p p p 是否有标记。有,就根据标记更新 p p p 的两个儿子,并往下增加标记,然后消除 p p p 的标记。

这种优化叫做延迟标记,是一个高效率的线段树应该具有的优化。

Part 3 ,Code

上代码。

线段树初始化:

struct segtree{int l,r,lazy,add;
}t[max_size<<1+max_size<<1];

有空更新一份用 c l a s s class class 写的。

从子节点往父亲更新标记:

void pushup(int id)
{t[id].add=t[id<<1].add+t[id<<1|1].add;
} 

下传标记:

void pushdown(int id,int l,int r){t[id<<1].lazy+=t[id].lazy;t[id<<1|1].lazy+=t[id].lazy;int mid=(l+r)>>1;t[id<<1].add+=t[id].lazy*(mid-l+1);t[id<<1|1].add+=t[id].lazy*(r-mid);t[id].lazy=0;	
}

线段树递归建立:

void build_segtree(int id,int l,int r){//节点id表示区间 [l,r]t[p].l=l,t[p].r=r;if(l==r){//叶节点t[p].add=a[l];return; }int mid=(l+r)>>1;build_segtree(id<<1,l,mid);build_segtree(id<<1|1,mid+1,r);//等同于build_segtree(id*2+1,mid+1,r); pushup(id);//标记延迟优化时间 
}

上传操作和下传操作是维护线段树的基本操作,基本上每一种操作都要用到。

还有,每个人的线段树写的都不一样,我的写法适中。所以,关于线段树,尽量不要背模板,要随机应变。

有一种比较经典的 s p r e a d spread spread 函数,其实就是 p u s h d o w n pushdown pushdown 函数。

单点修改和区间修改中很重要的一点:

判断是否向下递归,需判断覆盖性,如果当前区间 [ l , r ] [l,r] [l,r] 被完全覆盖,就可以直接返回了。就是一种整块的处理,有没有分块的感觉?

单点修改,将区间 [ x , y ] [x,y] [x,y] 的每一个值加上 v v v

int add_(int id,int l,int r,int x,int y,int v){if(x<=l&&r<=y){t[id].lazy+=v;t[id].add+=v*(r-l+1);return;//叶节点修改 }pushdown(id,l,r);int mid=(l+r)>>1;if(x<=mid)add_(id<<1,l,mid,x,y);if(y>mid)add_(id<<1|1,mid+1,r,x,y);pushup(id);
}

区间修改,求出区间 [ x , y ] [x,y] [x,y] 的每一个值的和:

int query(int id,int l,int r,int x,int y){if(x<=l&&r<=y){return t[id].add;}pushdown(id,l,r);int ans=0,mid=(l+r)>>1;if(x<=mid)ans+=query(id<<1,l,mid,x,y);if(y>mid)ans+=query(id<<1|1,mid+1,r,x,y);return ans;
}

有了这些操作,就可以实现大部分的线段树题目了。

-----------------------咕咕的分界线------------------------------------------------

Part 4 例题及题解

1.最经典的线段树1,只要把上面代码组合一下就可以过了。此处不贴。

2.守墓人,把上面的代码组合修改就可以过了。我是用分块AC的,代码传送门。

3.题目是一道简单的省选,最大数,代码传送门我的博客(题解)。

4.线段树2,这道题难就难在区间乘。为了时间和精度,我们使用两个 l a z y t a g lazytag lazytag,乘法优先更新,就可以跑过了。

证明:乘法优先的话,不会改变加法的 l a z y t a g lazytag lazytag
的值,使得精度等一系列更优。证毕。

5.最难的一题:线段树3,由于有历史情况,所以我们要维护4种标记,然后跑过。代码和 灵梦 的非常像,但没有抄袭,我是对着TA的打了一遍,那时,我刚学线段树。代码:传送门。

-----------------------咕咕的分界线------------------------------------------------

结:

我希望这篇文章对读者有所帮助。若有建议,可直接在评论区提出。转载私聊。相对来说,这篇文章很详细,因为我自己学这个的时候,别的文章大都有些欠缺,就像那位基友的,导致我不好理解。最近有空,就写篇真正的好文章详解吧!!!

如果觉得这篇文章对您有帮助,可以点个赞吗???谢谢阅读。

这篇关于超级用心的线段树介绍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/326396

相关文章

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

zookeeper端口说明及介绍

《zookeeper端口说明及介绍》:本文主要介绍zookeeper端口说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、zookeeper有三个端口(可以修改)aVNMqvZ二、3个端口的作用三、部署时注意总China编程结一、zookeeper有三个端口(可以

Python中win32包的安装及常见用途介绍

《Python中win32包的安装及常见用途介绍》在Windows环境下,PythonWin32模块通常随Python安装包一起安装,:本文主要介绍Python中win32包的安装及常见用途的相关... 目录前言主要组件安装方法常见用途1. 操作Windows注册表2. 操作Windows服务3. 窗口操作

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

HTML img标签和超链接标签详细介绍

《HTMLimg标签和超链接标签详细介绍》:本文主要介绍了HTML中img标签的使用,包括src属性(指定图片路径)、相对/绝对路径区别、alt替代文本、title提示、宽高控制及边框设置等,详细内容请阅读本文,希望能对你有所帮助... 目录img 标签src 属性alt 属性title 属性width/h

MybatisPlus service接口功能介绍

《MybatisPlusservice接口功能介绍》:本文主要介绍MybatisPlusservice接口功能介绍,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录Service接口基本用法进阶用法总结:Lambda方法Service接口基本用法MyBATisP

MySQL复杂SQL之多表联查/子查询详细介绍(最新整理)

《MySQL复杂SQL之多表联查/子查询详细介绍(最新整理)》掌握多表联查(INNERJOIN,LEFTJOIN,RIGHTJOIN,FULLJOIN)和子查询(标量、列、行、表子查询、相关/非相关、... 目录第一部分:多表联查 (JOIN Operations)1. 连接的类型 (JOIN Types)

java中BigDecimal里面的subtract函数介绍及实现方法

《java中BigDecimal里面的subtract函数介绍及实现方法》在Java中实现减法操作需要根据数据类型选择不同方法,主要分为数值型减法和字符串减法两种场景,本文给大家介绍java中BigD... 目录Java中BigDecimal里面的subtract函数的意思?一、数值型减法(高精度计算)1.

Pytorch介绍与安装过程

《Pytorch介绍与安装过程》PyTorch因其直观的设计、卓越的灵活性以及强大的动态计算图功能,迅速在学术界和工业界获得了广泛认可,成为当前深度学习研究和开发的主流工具之一,本文给大家介绍Pyto... 目录1、Pytorch介绍1.1、核心理念1.2、核心组件与功能1.3、适用场景与优势总结1.4、优

Java实现本地缓存的常用方案介绍

《Java实现本地缓存的常用方案介绍》本地缓存的代表技术主要有HashMap,GuavaCache,Caffeine和Encahche,这篇文章主要来和大家聊聊java利用这些技术分别实现本地缓存的方... 目录本地缓存实现方式HashMapConcurrentHashMapGuava CacheCaffe