Segment Tree Beats 学习笔记
例1
我们对一个区间 \([l,\ r]\) 维护它的最大值 \(mx\),以及 \(mx\) 的出现次数 \(cnt_mx\),和严格次大值 \(se\)。并且维护区间和 \(sum\) 来计算答案。
对于 \(\forall\ l\ \leq\ i\ \leq\ r\ a_i\ =\ min(a_i,\ x)\) 操作时,如果 \(x\ \geq\ mx\) 则什么都不做,如果 \(x\ >\ se\),则修改 \(mx\) 和 \(sum\) 即可。否则递归下去。注意递归前要进行 push 操作,递归后要进行 pull 操作。
可以将每个节点的 \(mx\) 理解为对这个节点的子树内所有节点进行 \(min(a_i,\ mx)\) 的标记。复杂度 \(O(n\ log\ n)\)。
代码
例2
由于对每个区间只有当 \(mx,\ mn\) 都存在时才能算贡献,不妨建两棵线段树。一棵线段树初始时 \(u_i\ =\ 0,\ d_i\ =\ 0\),另一棵 \(u_i\ =\ \infty,\ d_i\ =\ -\infty\)。这样在一段区间可以算答案的时候暴力将第一棵树的对应节点赋值为第二棵线段树的对应节点。注意 pull 和 初始话 的时候 \(mx,\ mn,\ semx,\ semn\) 不能设为 \(0\) 而要设为 \(\infty,\ -\infty\),因为存在 \(a_i\ =\ 0\) 的点。
代码
例3
观察 cmin 操作发现若区间 \(mx1\ \leq\ x\) 则啥都不做,若 \(mx2\ <\ x\) 则只用改 \(mx1\) 的值,否则直接暴力递归。因此可以将 cmin 操作转化为将区间内最大值 \(+\ y\)。所以每个区间只需要维护 \(tag\) 表示整体加减的懒标记,\(len,\ sum\),以及 \(mx1,\ mx2,\ =\ mx1\) 的个数 \(mxc\),将区间内值 \(=\ mx1\) 的数 \(+\ x\) 的懒标记 \(mxt\),\(mn1,\ mn2,\ =\ mn1\) 的个数 \(mnc\),将区间内值 \(=\ mn1\) 的数 \(+\ x\) 的懒标记 \(mnt\)。也就只有三种操作,区间内所有数 \(+\ x\),区间内值等于最大数的所有数 \(+\ x\),区间内值等于最小数的所有数 \(+\ x\)。在实现的时候注意考虑区间只有 \(1\) 个数和只有 \(2\) 个数的情况。在实现 \(cmax,\ cmin\) 函数时,有个技巧,当 \(a\ \leq\ l\ \leq\ r\ \leq\ b\) 且需要继续递归儿子的时候不 return 就行了。
代码
例4
对每个 \(i\) 维护一个函数 \(a_i\ =\ f_i(0)\),可以发现每个 \(f(x)\) 都可以写成 \(f(x)\ =\ max(x\ +\ k,\ c)\) 的形式。初始化时 \(f_i(x)\ =\ max(a_i,\ x\ -\ \infty)\)。记对 \(i\) 进行过的所有操作为 \(f_{k_1}(x),\ f_{k_2}(x),\ ...,\ f_{k_m}(x)\)。则 \(a_i\) 当前的值为 \(f_{k_m}(f_{k_{m\ -\ 1}}(\ ...,\ f_{k_1}(x)))\),\(a_i\) 的历史最大值为 \(f_{k_t}(\ f_{k_{t\ -\ 1}}(\ ...,\ f_{k_1}(x))),\ 1\ \leq\ t\ \leq\ m\) 中最大的。
我们对每个节点维护从上一次 push 之后到现在的当前值函数 \(f_{cur}(x)\),在修改的时候直接求 \(f(\ f_{cur}(x))\) 即可。求历史最大值我们还需要维护 \(g(x)\),记上一次 push 到现在当前值所经历的函数分别为 \(f_1(x),\ f_2(x),\ ...,\ f_s(x)\),则 \(g(x)\ =\ \max_{1\ \leq\ i\ \leq\ s}\ \{f_i(x)\}\)。在修改时先修改 \(f_{cur}(x)\),再做 \(g(x)\ =\ \max(g(x),\ f_{cur}(x))\)。由于子树中的 \(x\) 可能不同,所以这样做可以把所有 \(x\) 的历史最大值都取到,可以想象成分段函数取上届。
push 操作时,先修改儿子 \(ch\) 的 \(g(x)\) 值,\(g_{ch}(x)\ =\ \max(g_{ch}(x),\ g(f_{ch}(x)))\),再做 \(f_{ch}(x)\ =\ f(f_{ch}(x))\)。
注意实现的时候要注意不要爆 long long。
代码
例5
在例4基础上由于 \(f(x)\ =\ max(x\ +\ k,\ c)\) 单调不减,所以直接对每段区间维护最后一次遍历到这个节点的时候最大值和历史最大值时多少即可。被打懒标记或进行操作时,历史最大值 \(his_{max}\) 变成 \(max(his_{max},\ g(cur_{max}))\),当前最大值 \(cur_{max}\) 变成 \(f(cur_{max})\)。
代码
例6
与例4、例5不同的是这一题区间历史最值与 cmax 操作方向相反。考虑类似 例3 做法,对每段区间维护最小值、次小值,将 \(cmax\) 操作转变为对 \(val\ =\ min\_ number\) 的所有数做加法。对最小值和非最小值分别维护上一次 push 操作到现在的历史最小懒标记值,以及答案即可。