解题报告-论对“多序列线段树”的新理解
解题报告-论对“多序列线段树”的新理解
简单的线段树不考多序列,多序列线段树都不是普通线段树。
多序列线段树,指在多个序列上分别修改但是合起来查询的线段树。常见的查询方式有 \(\max(\{A+B\}),\max(\{\min(a,b)\})\) 等。
首先,这种“多序列”问题的序列一定不会很多。如果很多了,那就成线段树套线段树了,多序列线段树也就失去了意义。但是这种线段树的序列个数不多不少,刚好 \(2\sim 4\) 个,可以视为常数。
说是常数,查询起来是很恶心的。多序列线段树一般由 \(\texttt{SegmentTreeBeats}\) 维护。上次我们说区间历史最值的时候提到过,一旦某道题里面出现了最值,那么几乎所有值都要记录一个最值。这一次也不例外。
还是从最简单的情况开始想吧。如果我们只有一个序列,我们只需要记录两个 \(tag\),针对最大值的和针对非最大值的。但是到了多序列上,情况就复杂多了,难道我们真的要分开对每一个区间进行操作,最后直接无脑合起来?
这样做是错误的!记住这个坑,它会让你挂掉一半以上的分。以 \(\max(\{A+B\})\) 的询问为例,多序列意义下会有很多最大值都可以作为答案。那么这个时候,比如 \(7+2,6+3,5+4\)。
那么现在,问题来了,我们要对 \(A\) 序列中的这三个位置进行取 \(\min\) 操作,进行 \(\min(A_i,6)\),那你说这个时候线段树上的 \(max_{A+B}\) 是更新还是不更新呢?更新也不对,不更新也不对。
所以我们来说正确的解法。他说多序列,我直接面向多序列编程不就行了?在一个序列中,值分为 \(2^1=2\) 种(是最大值的和不是最大值的),但是在 \(k\) 个序列中,值分为 \(2^k\) 种,我们直接暴力枚举这些种类并记录对应的 \(tag\) 就行了。时间复杂度 \(O(2^kn\log n)\)。由于 \(k\) 一般不会超过 \(5\),所以还是可以视为常数。
pushup
还是类比原版 \(\texttt{SegmentTreeBeats}\),如果左子的最大值不是区间最大值,那么左子的最大值和右子的次大值比较得到大区间的次大值。这里也是一样,如果左子的某个序列的最大值不是大区间这个序列的区间最大值,那么其记录的一切跟该序列最大值有关的答案只能去更新大区间该序列的非最大值的答案。但是如果左子某个序列的最大值是大区间的区间最大值就不一样了,这个时候不要手滑更新到非最大值上了。
pushdown
直接对应 \(tag\) 对应下放就行,但是 tag
的个数是 \(O(kt)\) 而不是 \(O(2^kt)\) 的,其中 \(t\) 是操作的种类数量。那么这就又回到了 \(\texttt{SegmentTree}\) 的一个基本性质:pushup
一般是用来求答案的,pushdown
一般是用来更新的。更新分开更,答案一起求,所以有了对应的 \(tag\) 个数。