吉司机线段树学习笔记
吉司机线段树
处理 区间最值操作于历史最值问题的数据结构
参考著名ppt \(Segment\ Tree\ Beats\) 与jry2016年论文
大概很久之前就写了大部分,最近又会了点,但离全部学会还有一定距离
区间最值操作
Problem 1
维护一个序列,支持区间取 \(\min\) ,区间求和,求 \(\max\)
做法
线段树上维护区间和 \(sum\),最大值 \(mx\),次大值 \(sc\) 与最大值出现次数 \(cnt\)
区间对 \(x\) 取 \(\min\) 操作暴力递归区间
- \(mx\leq x\),结束递归
- \(se\leq x<mx\),将区间最大值修改为 \(x\),更改区间和,打上区间取 \(\min\) 标记
- \(x<se\),暴力递归左右儿子
时间复杂度 \(O(nlogn)\)
时间复杂度分析
设势能函数 \(\Phi(T)\) 表示线段树中每个节点维护的区间最大值是否与父亲的最大值相同
将每一个节点的贡献记在其对应的叶子节点上,考虑一个叶子最多只存在一次贡献
故 \(\Phi(T)\leq n\)
考虑每一次暴力区间取 \(\min\) 对势能函数的影响
若该次操作为对 \(v\) 取 \(\min\),且在节点 \(x\) 处停止,则 \(se_x\leq v<se_{fa_x}\)
- \(mx_x<mx_{fa_x}\),则 \(mx_x=se_{fa_x}\),那么 \(mx_{fa_x}\to v,mx_x\to v\),\(\Phi(T)\) 将减少1
- \(mx_x=mx_{fa_x}\),对于此种情况 \(mx_{fa_x}\to v,mx_x\to v\),\(\Phi(T)\) 无影响
考虑修改过程中的每一个1类点到根的路径最多存在 \(logn\) 个2类点
那么时间复杂度 \(O(nlogn)\)
Problem 2
维护一个序列,支持区间取 \(\min\) ,区间加,区间求和,求 \(\max\)
做法
沿用 Problem 1中的做法,多维护一个懒标记处理区间加
时间复杂度分析
考虑沿用 Problem 1中的证明,考虑一次区间加最多使 \(\Phi(T)\) 增加 \(logn\)
可得时间复杂度 \(O(nlog^2n)\)
吉老师在其论文中提到这个做法的实际表现近似于 \(O(nlogn)\),目前未有方法证明该结论
Problem 3
维护一个序列,支持区间取 \(\min,\max\),区间求和
做法
类似 \(\min\) 的维护,\(\max\) 同理
时间复杂度分析
\(O(nlog^2n)\)
Problem 4
维护一个序列,支持区间取 \(\text{and},\text{or}\),区间求 \(\max\)
做法
考虑如何优化暴力
对于 \(\text{and}\ v\) 操作
-
若区间内 \(\text{or}\) 和 \(so\) 是 \(v\) 的子集,可直接返回
-
若区间内 \(\text{and}\) 和 \(sa\) 与 \(v\) 的交集与 \(or\) 和 \(so\) 与 \(v\) 的交集相同
则均为1的bit将全部保留,仅部分为1的bit将去除,最大值直接与 \(v\) 取交即可,并打上懒标记
对于 \(\text{or}\ v\) 操作类似处理
时间复杂度分析
对线段树上的每个节点 \(u\) 设一个势能函数 \(\Phi(u)=\Phi(ls_u)+\Phi(rs_u)+\sum_{i=0}^w[第i个bit在区间内不全相同]\)
显然 \(\Phi(root)=O(nw)\)
考虑每一次到达被询问区间包含的点 \(u\) 后继续递归,代表这个区间内仍存在至少一个 \(bit\) 不全相同
(\(\text{and}\ v\) 操作等价于 \(v\) 为0的bit全变0,\(\text{or}\ v\) 操作等价于 \(v\) 为1的bit全变1)
那么每遍历一个节点 \(\Phi(root)\) 将减少1
考虑势能将在仅有部分区间被操作点处增加
众所周知,线段树上定位一个区间需遍历 \(O(logn)\) 个点,这些点可能会存在原本全相同的bit被改不相同的情况
每个操作最多给 \(\Phi(root)\) 带来 \(O(wlogn)\) 的增长
则最终复杂度为 \(O(qlogn+nw+qwlogn)\)
历史版本问题
大致分为 历史最大值,历史最小值,历史版本和
均为字面意思
Problem 1
对于一个序列,支持区间加,区间覆盖,查询区间最大值,查询区间历史最大值
做法
我们先忽略区间覆盖,完成这个问题
对于一次区间加操作,传统的做法是在线段树上维护一个懒标记
我们考虑对懒标记的一种新理解
实际上,我们的懒标记是每个线段树节点上维护的一个操作序列
而进行区间加操作时,我们对懒标记的修改相当于给操作序列后面添加一个数
由于下传标记的及时性,我们每次下传懒标记相当于把整个操作序列加入到儿子操作序列的末尾
那么因为我们只想知道区间和,我们仅需维护操作序列的总和即可,也就是 \(Add_u\)
我们考虑怎样求历史最大值
对于一个线段树上的区间,实际上操作序列对历史最大值的贡献即区间最大值加上操作序列最大的前缀和
我们定义 \(Pre_u\) 表示节点 \(u\) 的操作序列的最大前缀和,维护 \(hmx_u\) 表示节点 \(u\) 所代表区间的历史最大值
同样维护操作序列总和 \(Add_u\)
考虑下传操作序列的操作,对于 \(u\) 的儿子 \(v\)
\(Pre_v=\max\{Pre_v,Add_v+Pre_u\},Add_v=Add_v+Add_u\),这很好理解
考虑上传信息的操作 ,对于 \(v\) 的父亲 \(u\)
\(mx_u=\max\{mx_v\},hmx_u=\max\{hmx_v\}\)
如何同时处理区间覆盖操作呢?
我们考虑对于一个节点的操作序列,在进行第一次区间赋值操作后之后未下传之前的所有其它操作都会成为区间赋值
我们其实可以理解为多增加一种操作序列表示区间赋值
那么我们多维护一个 \(Iscov_u\) 表示节点 \(u\) 是否有赋值的操作、 \(cov_u\),表示赋值操作的对象以及 \(hcov_u\),表示历史最大 \(cov_u\) 值
现在此题就解决了!时间复杂度 \(O(m\log n)\)
Problem 2
对于一个序列,支持区间减后对 0 chkmax,区间加,区间赋值,询问单点值,询问单点历史最大值
做法
我们考虑操作的形式
\(x'=\max\{x-a,0\},x'=x+a,x'=a\)
由于第一种操作形式特殊,我们试图把三种操作规约起来
即 \(x'=\max\{x+a,b\}\)
那么操作一的 \(a,b\) 对显然是 \((-a,0)\),操作二则是 \((a,-inf)\),操作三为 \((-inf,a)\)
我们现在就把所有操作转化为 \((a,b)\) 的运算了
考虑定义 \((a,b)+(c,d)\)
将 \(x'=\max\{\max\{x+a,b\}+c,d\}\),改写成 \(x'=\max\{x+a+c,\max\{b+c,d\}\}\)
所以 \((a,b)+(c,d)=(a+c,\max\{b+c,d\})\)
定义 \(\max\{(a,b),(c,d)\}\)
将 \(x'=\max\{\max\{x+a,b\},\max\{x+c,d\}\}\) 改写成 \(x'=\max\{x+\max\{a,c\},\max\{b,d\}\}\)
所以 \(\max\{(a,b),(c,d)\}=(\max\{a,c\},\max\{b,d\})\)
此时我们直接像 Problem 1 一样维护操作序列总和、操作序列前缀和最大值即可
时间复杂度 \(O(m\log n)\)
实际上,这个问题也支持区间询问最大值与历史最大值,考虑把初始序列看成操作即可
Problem 3
对于一个序列,支持区间加,查询区间历史版本和
\(\texttt{Solution}\)
可以使用懒标记解决
考虑将一个修改 \([l,r,x]\),变成 \([1,l-1,0],[l,r,x],[r+1,n,0]\)
对于一个线段树区间 \([l,r]\)
维护 \(v_0=r-l+1,v_1=\sum_{i=l}^r a_{t,i},v_2=\sum_{j=1}^t\sum_{i=l}^ra_{j,i}\)
考虑 \(+x\) 带来的影响
\(v_0'=v_0\\v_1'=v_1+v_0x\\v_2'=v_2+v_1+v_0x\)
是一个线性变换,可以表示成矩阵的形式,那么懒标记用转移矩阵表示即可
复杂度 \(O(n\log n)\)