单侧递归线段树
一侧儿子会影响另一侧儿子的信息
例 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[l:r]\) 中大于 \(v\) 的前缀最大值数量
令 \(M\) 为区间 \([l:r]\) 的中点,则
于是可以 \(O(\log n)\) 求出一个 \(Q(v,l,r)\)
单点修改 \(\text{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\)
离线操作,对栈进行扫描线,转化为给定数组 \(v_{1\sim m}\),有数组 \(a_{1\sim m}\),若干次操作为 \(a\) 的单点修改,或给定 \(ct\) 和 \(R\) 查询 \(\sum_{i=1}^R v_i \max(0,\min(\text{tmp}(i,R),ct-\sum_{j=1}^{i-1} \text{tmp}(j,R)))\),其中 \(\text{tmp}(i,R)\) 为 \(a[i:R]\) 的最小不可空前缀和与 \(0\) 的 \(\max\),表示此时第 \(i\) 个操作到第 \(R\) 个操作时能剩下的数量
以下用 \(ar[L:R]\) 表示保存在线段树 \([L:R]\) 区间上的 \(ar\) 数组值,令 \(M\) 为区间中点,左右子区间为 \([L,M]\) 和 \((M,R]\)
令 \(sm[L:R]\) 为 \(a\) 的区间和,\(mn[L:R]\) 为区间最小可空前缀和,\(mx[L:R]\) 为区间最大可空后缀和
令 \(tt[L:R]=\sum_{i=L}^R v_i tmp(i,R)\),令 \(Lt[L:R]=tt[L:R]-tt[M+1:R]\)
定义函数 \(\text{Dv}(ad,L,R)\),其中 \([L:R]\) 为线段树的一个结点,\(ad\ge 0\),令 \(\text{Dv}(ad,L,R)=\sum_{i=L}^R v_i \max(0,\min(\text{tmp}(i,R),\sum_{j=i}^R \text{tmp}(j,R)-ad))\)
定义函数 \(\text{gtmn}(L,R)\) 返回一个二元组,其中 \([L:R]\) 不一定为线段树上的结点,返回值的第一个数意义同 \(mn\),第二个数意义同 \(sm\)
定义函数 \(\text{qry}(ct,R,ad,l,r)\),其中 \(ad=\text{gtmn}(r+1,R)[mn]\)(默认空区间的 \(mn\) 为 \(0\)),\([l:r]\) 为线段树上的区间,\(ad\le 0\),令 \(\text{qry}(ct,R,ad,l,r)=\sum_{i=l}^{\min(r,R)} v_i\max(0,\min(\text{tmp}(i,R),ct-\sum_{j=l}^{i-1}\text{tmp}(j,R)))\),\(ad\) 用于辅助计算
实现 \(\text{push\_up}\) 后容易实现 \(a\) 的单点加,容易由 \(\text{qry}\) 得到询问的式子的值
考虑如何实现上述函数和维护数组
\(sm,mn,mx\) 在 \(\text{push\_up}\) 时容易 \(O(1)\) 维护,\(\text{gtmn}\) 容易做到单次 \(O(\log n)\)
有以下等式:
- \(mn[L:R]+mx[L:R]=sm[L:R]\)
- \(mx[L:R]=\sum_{j=L}^R \text{tmp}(j,R)\)(\(L\) 到前缀和最小值处的 \(\text{tmp}\) 都是 \(0\),设剩余部分中选出若干位置 \(p_{1\sim t}\),满足这些位置上的 \(a\) 的前缀和递增,且 \((p_i,p_{i+1})\) 内的 \(a\) 的前缀和都大于 \(p_i\) 的 \(a\) 的前缀和,这样 \(p_i\) 处的 \(\text{tmp}\) 等于 \([p_i,p_{i+1})\) 的区间和,\((p_i,p_{i+1})\) 内的 \(\text{tmp}\) 都是 \(0\),即后缀最大值部分的每个 \(a_i\) 恰好计入总和一次)
先考虑如何实现 \(\text{Dv}(ad,L,R)\)
- 当 \(ad=0\) 时直接返回 \(tt[L:R]\)
- 当 \(L=R\) 时返回 \(v_L \max(0,\text{tmp}(L,R)-ad)\)
- 若 \(mx[M+1:R]=\text{tmp}(j,R)\ge ad\),则返回 \(\text{Dv}(ad,M+1,R)+Lt[L:R]\)
- 否则 \([M+1:R]\) 内的 \(tmp\) 都是 \(0\),同时 \([M+1:R]\) 对 \(ad\) 抵消的值为 \(sm[M+1:R]\),返回 \(\text{Dv}(ad-sm[M+1:R],L,M)\)
显然可以 \(O(\log n)\) 计算一个 \(\text{Dv}\)
然后考虑实现 \(\text{push\_up}\),此时只需要考虑 \(Lt\) 的维护,其他值的维护都是容易的
- 当 \(mn[M+1:R]=0\) 时,右侧对左侧没有影响,此时 \(Lt[L:R]=tt[L:M]\)
- 否则 \(Lt[L:R]=\text{Dv}(-mn[M+1:R],L,M)\)
最后考虑实现 \(\text{qry}(ct,R,ad,l,r)\)
- 当 \(ct=0\) 时显然返回 \(0\)
- 当 \(l=r\) 时返回 \(v_l\max(0,\min(ct,sm[l:r]+ad))\)(假定 \(l\le R\))
- 当 \(R\le M\) 时返回 \(\text{qry}(ct,R,ad,l,M)\)
- 以上都是显然的
- 若不满足,令 \(mn=\text{gtmn}(1,R)[mn],sm=\text{gtmn}(M+1,\min(r,R))[sm]\),令 \(Rt=\min(mn,sm+ad)\) 为区间 \([M+1,R]\) 的 \(mn\)
- 若 \(ct<Rt+mx[l:M]\),则右区间对答案没有贡献,返回 \(\text{qry}(ct,R,Rt,l,M)\)
- 否则返回 \(\text{Dv}(-Rt,l,M)+\text{qry}(ct-\max(0,mx[l:M]+Rt),R,ad,M+1,r)\)
总时间复杂度 \(O(n\log^2n)\)(假设 \(n,q\) 同阶)
参考
- \(\text{2024.12.13 EZDS.pdf\;\;\; by Luzhuoyuan}\)