首页 > 其他分享 >线段树

线段树

时间:2024-02-15 11:11:06浏览次数:29  
标签:结点 线段 修改 tag 区间 节点

【前置知识】

运算律,单位元。

运算律基本就是交换律、结合律、分配律。

单位元:

假设我们现在有一个运算 \(\bigoplus\)。

如果 \(a\bigoplus b=b\bigoplus a=a\),称 \(b\) 是运算 \(\bigoplus\) 的单位元。

【线段树】

线段树是一个树形数据结构。

  1. 满二叉树;

  2. 倍增的思想,分治的手法;

  3. 点/区间进行查询/修改;

  4. 常考,码量大。

【线段树的设计】

为了满足线段树满二叉树的定义,我们先把数组补成 2的幂 个。

补充的数肯定不能影响我们的计算,于是我们把它们设成单位元。

记 \(n\) 为补充后的元素个数。

我们让这 \(n\) 个元素组成线段树的叶结点们。

递归建立线段树:

  1. 如果当前节点就是叶结点,用给好的元素组成;

  2. 否则递归建立左右子结点,然后用子结点的属性更新自身。

根结点代表区间 \([1,n+1)\)。

凡是有子结点的结点,设其代表区间 \([l,r)\),\(m=\frac{(l+r)}{2}\),则其左儿子代表区间 \([l,m)\),右儿子代表区间 \([m,r)\)。

注意:线段树有 \(2n-1\) 个结点,而一开始可能补了一倍,再翻倍,说明线段树最多会开出四倍的空间。

【单点查询】

不妨我们现在查询 \(p\) 号元素的属性。

递归查询:

  1. 如果当前节点是叶子结点,返回当前节点的属性;

  2. 否则设当前节点代表区间 \([l,r)\),\(m=\frac{(l+r)}{2}\),如果 \(p<m\),那么递归进入左子结点查询;否则进入右子结点。

复杂度:显然每次区间长度除以 2,\(O(\log n)\)。

【单点修改】

与单点查询差别不大。

  1. 如果当前节点是叶子结点,返回当前节点的属性;

  2. 否则设当前节点代表区间 \([l,r)\),\(m=\frac{(l+r)}{2}\),如果 \(p<m\),那么递归进入左子结点查询;否则进入右子结点。

  3. 递归结束之后,重新更新当前节点的属性。

注意最后要重新更新。

复杂度:同上,也是 \(O(\log n)\)。

【区间查询】

设我们要查询区间 \([l,r)\)。

  1. 如果当前结点代表的区间完全被 \([l,r)\) 包含,直接返回当前节点的属性;

  2. 如果当前节点代表的区间与 \([l,r)\) 完全无交,直接返回单位元;

  3. 否则递归查询左右儿子的答案,然后合并左右儿子的答案并返回。

复杂度:还是 \(O(\log n)\)。

注意到一个性质:

一个区间,只有包含左右端点的结点可能递归。

那么每层递归的区间个数最多两个。

也就是说我们最多需要处理四个。

递归到叶结点,也只是 \(O(\log n)\),省略 4 的常数,整体复杂度还是 \(O(\log n)\)。

到这里,看做题总结的【无区间修改】部分。

【区间修改】

树状数组也可以做到区间查询、单点修改,但是以上都没有做到。

现在,我们来讲一讲线段树的精华—— lazy tag

【lazy tag】

先说一说想法。

区间查询的瓶颈在于,我们需要深入到每个叶结点进行修改。

于是,我们决定不要修改所有叶结点,而是把修改的信息记录下来,等到需要的时候再拿出来用。

于是,lazy tag 就诞生了。

lazy tag,又称懒标记,延迟标记。

每个结点上都有一个 lazy tag,记录着节点代表区间的修改信息。

比如,我现在有一个 8个元素的数组。

现在,我想把 \([1,5)\) 加 4,我就只把 \([1,5)\) 对应结点的 lazy tag 加上 4,并且把该节点的和也加上 4 × 4。

很显然,如果我要修改 \([2,6)\) 的话,就没有现成的结点了。

因此我们有了一个修改的思路:

  1. 如果当前区间完全被修改区间包含,直接在本结点上更新标记;

  2. 如果当前区间与修改区间完全无交,直接返回;

  3. 否则递归给左右儿子修改,并且改完之后用左右儿子的属性更新本身。

而查询,我们也顺水推舟:

单点:

  1. 如果当前结点是叶结点,返回当前结点的值加 tag;

  2. 哪边有要查询的去哪;

  3. 返回的时候返回 子结点返回的结果+本结点的tag。

区间:

  1. 如果当前区间被完全包含,直接返回当前节点的属性;

  2. 如果完全无交,返回单位元;

  3. 递归查询左右儿子,并应用本结点的tag。

【pushdown】

但是,我们发现,如果按上面的方法做,可能会出一些问题。

比如赋值运算,新来的修改可能比老修改更靠下,这样在一路应用回去的时候就会被覆盖。

本质在于赋值运算不满足交换律

于是我们想了一个办法:只要新来的修改到达一个结点,立刻把这个结点的标记分发给左右儿子,直到新修改完毕。

这样操作我们就可以把交换律的问题解决。

去看【有区间修改】

【总结】

三个律:结合律,交换律,分配律。

三个运算:val+val,val×tag,tag×tag。

val + val:结合律。(小区间拼成大区间)

tag × tag:结合律。(val*tag1*tag2=val*(tag1*tag2))

pushdown:为了解决修改不符合交换律的问题。

区间修改:需要满足修改运算对查询运算的分配律,不然小区间拼成大区间,直接 ×tag 没有分配律会出错。

应对措施:

如果没有 val+val 的结合律,区间查询就变成单点查询,再求和。

如果没有 tag×tag 的结合律,在每个结点上记录一个 tag 数组。

如果没有交换律,pushdown。

如果没有分配律,区间修改就变成单点修改。

【树链剖分】

分块思想:把整体分成一个个小块,这样我们每次处理的时候就可以整体处理。

我们考虑把整个树剖分成若干条链。

定义:

一个结点的子结点中,子树规模最大的子结点,称为该节点的“重子”,其余的称为“轻子”,叶结点的重子是本身。

显然一个结点恰有一个重子。

记录重子,我们可以在每个结点的 vector 上通过 swap 把重子换到 0 号位置。

从一个结点通向其重子的边称为重边。

若干条连续重边连起来,就成了链。

树就被剖分成若干条链。

在每个节点上,记录 \(top\),表示当前节点所在链的链顶。

显然这是一个自上而下传递的属性。

因此我们需要两边搜索,第一次找重子,第二次找 top。

【树链剖分找LCA】

显然当两个结点在同一条链上时,它们的 LCA 是较高的那个点。

所以:当两个结点的 top 相等,返回较高的那个。

否则:因为只有两个结点在同一条链上时,才有可能一个是另一个的 LCA,于是我们可以让链顶较深的结点 \(x\) 跳到 \(p[top[x]]\),这样 \(x\) 就到了另一条链上。

循环往复,一定会到同一条链上。

【先重子深搜序】

我们深搜时优先走重子,得到了先重子深搜序。

记录 \(dfn\) 和 \(rev\),分别记录结点在先重子深搜序中的位置,和先重子深搜序中的位置对应的结点。

我们发现,一棵子树和一条树链,在深搜序中是连续的。

因此我们可以用线段树维护。

比如我们要把一条路径 \((u,v)\) 上的结点权值都+1。

按照上面找LCA的方法,只是在跳到链顶的时候利用线段树和 \(rev\) 数组把 当前节点 到 当前节点链顶 的这一段 进行区间修改。

注意:同链比自身深度,异链比链顶深度。左闭右开

复杂度分析:子树是一个区间,复杂度贡献 \(O(\log)\);如果要处理链,最多有 \(O(\log n)\) 个区间,因为每作为一次轻儿子,节点个数至少两倍。所以链贡献复杂度 \(O(\log^2 n)\)。

标签:结点,线段,修改,tag,区间,节点
From: https://www.cnblogs.com/FLY-lai/p/18016064

相关文章

  • 李超线段树
    李超线段树线段树的扩展版本用来动态维护平面上直线支持插入直线/线段,查询横坐标某处覆盖的线中纵坐标最大/最小值可以用于斜率优化DP插入直线这里直线是线段树上维护的信息,表示当前区间中点处最优的直线同时相当于区间修改,直线也是标记,这个区间内都可能有这条直线但是标......
  • 2024.2.14 WC24 线段树 / CF1572D / lgP3679
    西安Day1。感冒还没好,牙龈炎也没好,明天还要考试,又要坐牢了/kk。今天是tyy的图论选讲,感觉前面网络流部分还是在线的,平面图部分毒瘤tyy拿出了他的员交课件,恐怖,直接下线了。看了NAVI和Faze的预选赛,你们俩怎么都打的这么稀碎/fn。Override真好听。「WC2024」线段树我......
  • 线段树分治学习笔记
    线段树分治线段树分治是一种可以离线处理带撤销问题的常用手段。一般而言,题目中加入操作很好维护,但删除操作不好维护,这时可以对时间维建线段树,把每一个操作加入其存在时间段对应的线段树节点上,然后处理所有询问,进入一个节点时将这个节点里的操作加入,递归左右儿子,然后撤销这一次做......
  • 线段树进阶奇淫巧计
    看本文文字部分可以少带脑子,但是代码部分仔细看了因为不一定编译了不一定对动态开点一般来说线段树的空间开销是比较巨大的,需要\(4n\)的空间,一般其实是可以支撑的,但是权值线段树就不一定了。值域级别的代价是支持不了的。一般在动态开点的前提下只需要支持单点操作一旦是序......
  • 线段树
    3.区间操作与Lazy-Tag本节介绍线段树的核心操作Lazy-Tag,并给出区间修改\(+\)区间查询的模板。在线段树上,如果直接进行区间修改,时间复杂度为\(\mathcal{O}(N\logN)\)。而Lazy-Tag可以将其降为\(\mathcal{O}(\logN)\)。Lazy-Tag方法用\(lz_p\)统一记录区间\(p......
  • 线段树维护字符串哈希
    [ABC331F]PalindromeQuery#include<bits/stdc++.h>usingnamespacestd;#defineendl"\n"#defineintlonglongtypedeflonglongll;constintbase=131;constintp1=1222827239;constintN=1e6+100;intn,q,pn[N];strings......
  • 楼房重建线段树
    维护前缀最大值个数。对pushup操作进行修改。定义solve(x,lim)为前面这个区间的最大值为lim,\(x\)支配的区间产生的贡献。如果\(x\)的最大值已经小于\(lim\),显然没有贡献。考虑\(x\)的左儿子,如果左儿子的最大值大于\(lim\)直接递归左二子查询,此时右儿子的答案不......
  • P6025 线段树 题解
    解题思路暴力做法从\(l\)到\(r\)枚举每一个数,然后建线段树,记录最大下标,然后计算答案。代码略。时间\(O(n^2)\),期望得分:\(10\)分。优化暴力我们考虑每次枚举不遍历整棵线段树。显然,贪心的,最深的最右边的节点编号最大。那么我们可以发现,如果两颗子树大小相同,那么最大节......
  • P10145 [WC/CTS2024] 线段树 题解
    Link纪念一下场切题。题意:给定一棵(分点不一定为中点)的线段树,给定若干个询问区间,问有多少个线段树上结点的集合,知道了这些结点对应的区间和就可以知道任何一个询问区间的和。从询问区间开始考虑。会发现可以把所有\(a_i\)分成若干个集合,只要知道每个集合的和就可以知道所有询......
  • 洛谷 P10145 [WC/CTS2024] 线段树 题解--zhengjun
    提供一种考场做法,在思路上和官方题解的差异蛮大的,虽然结果差不多。首先需要发现\([l,r)\)区间可以算出来的充要条件是:如果对于每个选中的节点\(u\),连无向边\((L_u,R_u)\),则当且仅当\(l\)和\(r\)连通时区间\([l,r)\)可以算出来。证明的话,用前缀和理解这些东西,分别考虑......