2024.12.13 线段树、平衡树 讲课笔记

线段树分裂

模板题:P5494 【模板】线段树分裂

单次时间复杂度 \(O(\log V)\),其中 \(V\) 是被操作线段树的值域大小

线段树合并

模板题:P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并(需配合树上差分)

单次时间复杂度 \(O(\min(n,m)\log V)\),其中 \(n,m\) 分别为被合并的两棵树的叶节点数

若合并 \(k\) 棵线段树,值域都是 \(V\),叶节点数分别为 \(s_{1\sim k}\),则总时间复杂度为 \(O(\sum_i s_i\log V)\)(优于启发式合并的 \(O(\sum_i s_i\log\sum_i s_i\log V)\)

例 1:P5327 [ZJOI2019] 语言

给定一棵 \(n\) 个节点的树和 \(m\) 条链,求有多少无序点对 \((u,v)\;(u\ne v)\),满足存在一条链同时包含两者,\(n,m\le10^5\)

考虑对于每个 \(u\) 求出包含它的点对数量,答案即为 \(n\) 个点对应数量之和除以 \(2\)

包含 \(u\) 的合法 \((u,v)\) 的数量为经过 \(u\) 的所有链的并的大小减一

若干条链的并即为包含所有端点的最小联通子树

包含 \(a_{1\sim k}\) 的最小联通子树的大小为 \(\frac12(dis(a_1,a_k)+\sum_{i=1}^{k-1} dis(a_i,a_{i+1}))\),其中 \(a_{1\sim k}\)\(dfn\) 排序

\(dfs\) 给定树时,维护包含当前处理节点的所有链的端点的可重集

通过树上差分,问题转化为向集合中加入数、从集合中删除数、查询集合中相邻点的距离之和(假设集合按 \(dfn\) 排序,统计答案时要加上第一个和最后一个的距离(这容易实现)并乘上系数)、合并集合(要将处理子树时的集合与处理其父亲时的集合合并)

这容易通过动态开点权值线段树实现,其中线段树的 \(p\) 下标存储 \(dfn\)\(p\) 的点的出现次数,非叶节点存储相邻点距离之和(平衡树也可以,且其空间复杂度更小)

若使用 \(O(n\log n)-O(1)\) \(lca\),则可以做到 \(O(n\log n)\);若使用 \(O(n)-O(\log n)\) \(lca\),则可以做到 \(O(n\log^2 n)\)(假设 \(n,m\) 同级)

代码

例 2:T158644 [QwQOI2020] III

给定一个长为 \(n\) 的排列,\(m\) 次操作,区间升序 / 降序排序,或查询某一位置的值,\(n,m\le10^5\)

考虑颜色段均摊

对于每个有序的子段,建立一棵权值线段树

查询某一位置,则找到所在段后线段树上二分

区间排序,则将两端的段分裂(由于每段内有序,因此分裂成两半等同于按给定值分裂),并将中间的所有段合并为一个新段

时间复杂度 \(O(n\log^2n)\)(假设 \(n,q\) 同阶)

代码

标记永久化

免去标记下传的时间复杂度,要求标记有交换律

例:P8543 「Wdoi-2」纯粹的复仇女神

给定 \(a_{1\sim n}\)\(c_{1\sim n}\)\(q\) 次询问 \(l,r\),每次求 \(\max_{k=1}^n(\min_{l\le i\le r,c_i=k}a_i)\)(若 \(\min\) 内没有符合要求的则为 \(0\)),\(1\le a_i,c_i\le n\le2\times10^5,q\le10^6\)

\([L_i,R_i]\) 为满足 \(\forall L_i\le j\le R_i\land c_j=c_i,a_j\ge c_i\) 的极大区间,其容易通过单调栈线性求出

则一个 \([L_i,R_i]\) 会使 \(L_i\le l\le i,i\le r\le R_i\) 的询问 \(l,r\) 的答案对 \(a_i\)\(\max\)

即若干次矩形取 \(\max\) 和单点查询,其中查询全在修改之后

因此离线所有询问,扫描线处理

此题时限较紧,需要用两个优先队列模拟支持 插入、删除、取最大 的可重集

代码

历史值问题

一般为历史最值或历史值求和

例:P4314 CPU 监控

区间加,区间赋值,查询区间最大,区间历史最大,\(n\le10^5,m\le10^5\)

线段树每个叶子维护向量 \(\begin{bmatrix}a_i\\b_i\\0\end{bmatrix}\),每个非叶子节点维护其区间内向量和(\((\max,+)\) 矩乘,向量加为取 \(\max\))和乘法标记,则所有操作都可以变为矩阵乘法的形式

时间复杂度 \(O(n\log n)\),常数略大

代码

单侧递归线段树

维护一侧儿子会影响另一侧儿子的信息

例 1:P4198 楼房重建

\(a_{1\sim n}\)\(m\) 次操作每次修改其中一个位置,并输出此时 \(\frac{a_i}{i}\) 的前缀最大值数量,\(n,m\le10^5\)

单侧递归线段树模板题

\(b_i=\frac{a_i}i\) 建立线段树,则每次修改一点或查询前缀最大值数量

\(mx[L:R]=\max_{i=L}^R b_i\)(保存在线段树的节点 \([L:R]\) 上,下同),\(res[L:R]\) 为区间 \([L:R]\) 的前缀最大值数量

定义 \(Q(v,l,r)\)\(b_i[l:r]\) 大于 \(v\) 的前缀最大值数量

\(M\) 为区间 \([l:r]\) 的中点,则

\[Q(v,l,r)=\begin{cases} 0&mx[l:r]\le v\\ 1&mx[l:r]>v,l=r\\ Q(v,M+1,r)&mx[l:r]>v,l\ne r,mx[l:M]\le v\\ Q(v,l,M)+res[l:r]-res[l,M]&otherwise \end{cases}\]

于是可以 \(O(\log n)\) 求出一个 \(Q(v,l,r)\)

单点修改 \(push\_up\) 的过程中,\(res[l:r]=res[l:M]+Q(mx[l:M],M+1,r)\)\(mx\) 的跟新是容易的

因此单点修改单次 \(O(\log^2 n)\)

时间复杂度 \(O(m\log^2n)\)

代码

例 2:QOJ [PKUWC 2024 Day 2] # 8229. 栈

\(n\) 个栈,\(q\) 次操作,向 \([l,r]\) 中的栈分别压入 \(x\)\(y\)\([l,r]\) 中的栈分别弹栈 \(x\) 次(弹栈次数可能超过此时元素数,此时视为清空栈),查询第 \(p\) 个栈中从下往上第 \(l\) 个到第 \(r\) 个元素的和,\(n,q\le10^5,x,y\le10^9\)

\(a_{i,j}\) 表示第 \(i\) 次操作对第 \(j\) 个栈的元素数量的变化量,\(v_i\) 表示第 \(i\) 次操作加入的元素值(如果非加入操作,则值为 \(0\)

则压栈可以视为 \(a\) 中宽为 \(1\) 的矩形加,\(v_i\) 中单点赋值,弹栈可视为 \(a\) 中宽为 \(1\) 的矩形减,查询可以写为关于 \(a\)\(v\) 的形式

离线操作,翻转时间轴和下标,从 \(1\)\(n\) 对栈扫描线,则转化为数列 \(a_{1\sim m}\),初始为 \(0\),要求单点加,对给定的 \(ct\)(询问的前缀长度,原本区间询问拆为两个前缀询问)和 \(T\)(询问的时刻)查询 \(\sum_{t=1}^T v_t \max(0,\min(Rs(t,T),ct-\sum Rs(1\sim t-1,T)))\)(令 \(Rs(t,T)=\max(0,-\sum a[1:t-1]+\min_{i=t}^T \sum a[1:i])\)

这可以通过单侧递归线段树实现

总时间复杂度 \(O(n\log^2n)\)(假设 \(n,q\) 同级)

代码

势能线段树

例 1:P4145 上帝造题的七分钟 2 / 花神游历各国

区间开平方根下取整,求区间和,\(n,m\le10^5,a_i\le10^{12}\)

若区间不全为 \(1\) 则暴力递归两个子区间,直到叶子,并在过程中维护区间和

时间复杂度 \(O(n\log n\log\log V)\)

代码

例 2:UOJ #228. 基础数据结构练习题

区间开平方根下取整,区间加,求区间和,\(n,m\le10^5,a_i\le10^{5}\)

若区间最大值最小值开根下取整后变化量相同则整体打上区间加标记,否则暴力递归

时间复杂度 \(O((n+m)\log n\log\log V)\)

代码

例 3:HDU 5306 Gorgeous Sequence

区间取 \(\min\),查询区间最大值,求区间和,\(n,q\le10^6,a_i<2^{32}\)

吉司机线段树模板题

KTT

动态一次函数区间最值的问题

例 1:P5693 EI 的第六分块

区间加正数,区间查询可空最大子段和,\(n,q\le4\times 10^5,|a_i|\le10^9\)\(1.8s,256M\)

\(KTT\) 模板

对于静态的最大子段和,做法为每个区间维护 \(sum,lmx,rmx,smx\),分别表示区间和,区间最大前缀和,区间最大后缀和,最大子段和

对于动态最大子段和,原来的每个值变为一个一次函数,纵截距为当前的值,斜率为区间加一且值指示的区间不变的情况下,该值的增加量(即代表的区间长度),同时每个节点额外维护一个值 \(x\),表示当前区间加 \(x\) 后上述四个信息中可能有至少一个代表的区间会改变

原本的值相加变为一次函数相加(纵截距、斜率分别相加),值取 \(\max\) 变为一次函数取 \(\max\)(取纵截距较大的,若相同则任意)

叶子节点的四个一次函数都设为 \(x+a\),其中 \(a\) 为当前叶子对应位置的值,叶子节点的 \(x\) 设为 \(\infty\)(即强制至少选一个位置,因此最终输出前,若允许空区间,则要对 \(0\)\(\max\),这样可以避免叶子处的特判)

父节点的 \(x\),等于两儿子的 \(x\),所有取 \(\max\) 时的一次函数的交点的横坐标 中的较小值(横坐标向下取整,若小于 \(0\) 则忽略,因为本质为射线的交点)

同时每个节点还要维护区间加标记,意义和一般的一样

\(pushup\) 时直接合并信息即可

定义对一个节点的普通加 \(v\) 操作为令该节点的 \(x\) 减去 \(v\),四个一次函数的纵截距分别加上其斜率乘以 \(v\),区间加标记增加 \(v\)

\(pushdown\) 时令两个儿子普通加上该节点的加法标记,并清空加法标记即可

区间加 \(v\) 时,对于每个被完全覆盖的线段树的节点:若其 \(x\) 值不小于 \(v\),则普通加;否则暴力递归两个子树(子树的增加值要在 \(v\) 的基础上加上当前区间的加法标记)后 \(pushup\),并清空加法标记。显然到叶子处就会停止递归,因此不用考虑边界情况

最终答案即为信息合并后 \(smx\) 的纵截距的值

正确性显然

可以证明,这样时间复杂度为 \(O(n+m\log^3 n)\),但跑不满,实际效率接近 \(O(n+m\log^2 n)\)

代码

例 2:P5073 [Ynoi2015] 世上最幸福的女孩

全局加减,查询区间可空最大子段和,\(n\le3\times10^5,m\le6\times10^5,|a_i|\le2\times10^9\)

由于 \(KTT\) 不支持加负数,因此计算出每个查询操作时,在最开始序列的基础上全局增加量总和,离线询问后,按该总和从小到大排序后处理,这样每次都是全局加正数,注意原数组要先加上第一个的全局增加量,然后再建树

时间复杂度 \(O(n\log^2 n)\) 但卡不满,空间线性

代码

题单 & 文章

KTT 好题

KTT 笔记

平衡树

FHQ-treap

基本操作为分裂和合并,大部分操作都是 \(O(\log n)\)

Splay

各操作均摊 \(O(\log n)\)

核心操作为通过旋转将节点 \(splay\) 到根,要求每次访问一个节点后都要将其 \(splay\)

除了 \(LCT\) 外,其所有操作 \(fhq-treap\) 都能实现,且支持可持久化,常数差距也不大

例:P2042 [NOI2005] 维护数列

插入,删除,区间赋值,区间翻转,求区间和,求最大子段和,保证任意时刻数字不超过 \(5\times10^5\) 个,插入数字绝对值不超过 \(10^3\),操作树不超过 \(2\times10^4\),总插入数字不超过 \(4\times10^6\)

平衡树模板

每个节点保存翻转标记和赋值标记,维护区间和与最大子段和

时间复杂度 \(O(N\log n)\),空间复杂度 \(O(n)\),其中 \(N\) 为操作的数字总数,\(n\) 为数列中数字数量的最大值,常数较大

代码

可持久化

例:P3168 [CQOI2015] 任务查询系统

\(n\) 个初始为空的集合,有 \(m\) 个三元组 \((l,r,x)\),表示集合 \(l\sim r\) 中插入 \(x\)(可重),\(n\) 次询问,查询第 \(x\) 个集合中前 \(k\) 小值之和,\(n,m\le10^5\),询问强制在线

从左到右扫描,\((l,r,x)\) 拆为扫到第 \(l\) 个时加入 \(x\),扫到第 \(r+1\) 个时删除 \(x\),可持久化值域线段树维护,查询时线段树上二分即可

时间复杂度 \(O((n+m)\log n)\)

代码

vjudge 练习

EZDS hw, pw:YuanShenWanWan

EZDS ex, pw:WanYuanWanDe

参考

EZDS.pdf by Luzhuoyuan

Splay 树 oi-wiki

posted @ 2024-12-29 17:24  Hstry  阅读(72)  评论(0)    收藏  举报