P5607 更优秀的做法

· · 题解

其他题解已经介绍了本题事实上等价于单点修改、求一个区间的线性基信息。这个问题以往的常规做法是直接建线段树,对每个节点维护对应区间的线性基信息,认为时间复杂度是 O(n \log^2 V + m \log n \log^2 V)(事实上关于 n 的复杂度可以分析到更低)。现在给出一种时间复杂度为 O(n \log \log V \log V + m (\log n \log V+\log^2 V)) 的做法。

我们先来研究一些更简单的问题。

考虑这样一个事实:假设你维护了线段树上所有节点的一组标准基,要支持单点修改。你其实不必认为某个维护 [l,r] 的节点要带删地维护长为 r-l+1 的序列的标准基,而可以认为它其实要维护它左右儿子的标准基拼起来得到的序列,的标准基,当然对于叶子还是维护它唯一那个值的标准基。

而后者的长度就只有至多 2\log V 了,我们那个 O(n) 的算法就非常有用武之地了。

于是我们得到了最终算法:对线段树上所有节点维护正向/反向的标准基,建立是 trivial 的,叶子节点直接拿过来,非叶子节点将左右儿子的标准基的各个元素依次插入即可。修改时,相当于先对叶子的值进行修改,这会对它两个方向的标准基造成若干次插入和删除,而这些插入和删除在这个叶子的父节点看来,相当于父节点要维护的序列发生了若干次插入和删除,依次类推。

然后我们知道事实上对一个序列的值做单点修改,对这个序列的标准基产生的影响是进行 O(1) 次插入和删除。所以把这些插入和删除事件做一些消去(要插入和删除相同的值可以都消去)就可以保证插入和删除事件每层都只进行 O(1) 次。

查询时,像上一个例题一样找到首次切开的位置 mid,相当于要查询 [L,mid],[mid+1,R] 两段区间的线性基信息。比如 [mid+1,R] 的部分,其实直接取出 [mid+1,r] 这一线段树节点维护的标准基中,所有编号 \le R 的元素,插入一个新的线性基即可。

这样修改和查询的复杂度都与上一例题保持不变:修改 O(\log n \log V),查询 O(\log^2 V)

然后我们对建立的复杂度进行更精细的分析:如果 n=2^k 的话,距离叶子为 d 的节点管辖的区间长度为 2^d,那么其建立时至多要向线性基插入 \min(2^{d-1},\log V) 个元素,而这样的节点有 2^{n-d} 个。

d \le \log \log V 的层估计成 2^{d-1},每一层插入次数总和不超过 nd > \log \log V 的层估成 \log V,这些层的总节点数不超过 2^{k-d+1}。这样前半部分是 O(n \log \log V) 次插入,后半部分是 O(n) 次插入,显然时间复杂度不超过 O(n \log \log V \log V)。事实上这里两边仍然不平衡,应该可以稍微更优一点。

最后还有一个优化:扔掉线段树上所有管辖区间长度 \le \log V 的节点,只有查询区间长度 \le \log V 时其才会在一个管辖区间长度 \le \log V 的节点处被切开,那么这些查询是可以直接暴力的。这样在时间复杂度上是常数优化,在空间复杂度上除了一个 \log V,做到了线性空间。

所以最后我们得到了这样一个做法:时间复杂度 O(n \log \log V \log V + m(\log n \log V + \log^2 V)),空间复杂度 O(n),并且可以强制在线。

给出一份可能实现得并不好的代码:

code