超级用心的线段树介绍

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

相关文章

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

hdu 1754 I Hate It(线段树,单点更新,区间最值)

题意是求一个线段中的最大数。 线段树的模板题,试用了一下交大的模板。效率有点略低。 代码: #include <stdio.h>#include <string.h>#define TREE_SIZE (1 << (20))//const int TREE_SIZE = 200000 + 10;int max(int a, int b){return a > b ? a :

hdu 1166 敌兵布阵(树状数组 or 线段树)

题意是求一个线段的和,在线段上可以进行加减的修改。 树状数组的模板题。 代码: #include <stdio.h>#include <string.h>const int maxn = 50000 + 1;int c[maxn];int n;int lowbit(int x){return x & -x;}void add(int x, int num){while

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}

poj 1127 线段相交的判定

题意: 有n根木棍,每根的端点坐标分别是 px, py, qx, qy。 判断每对木棍是否相连,当他们之间有公共点时,就认为他们相连。 并且通过相连的木棍相连的木棍也是相连的。 解析: 线段相交的判定。 首先,模板中的线段相交是不判端点的,所以要加一个端点在直线上的判定; 然后,端点在直线上的判定这个函数是不判定两个端点是同一个端点的情况的,所以要加是否端点相等的判断。 最后