关于区间 k 小值
多次询问 \([l,r]\) 中排名第 \(k\) 小的数的数值。
从下标考虑,二分答案转为判定;从数值考虑,每次二分答案在 \(mid\) 的哪一侧。
如果是二分答案,问题转化为询问 \([l,r]\) 中有多少数小于 \(t\)。
研究下标区间,可以转化为两个前缀和作差,或 \(\log\) 个区间相加。
前者单点更新是 \(O(n)\),每个数出现次数和是 \(O(n^2)\),后者单点更新是 \(O(\log n)\),所有数出现次数和是 \(O(n \log n)\)。
前者不能直接存所有数,必须转到值域上操作。使用主席树维护,二分、算答案,一共两个 \(\log\),明显可以用单 \(\log\) 的主席树上二分直接代替。当我们从值域本身统计多少下标对应的数落在某个值域区间时,自然可以直接与 \(k\) 比较来决定跳至哪一个值域区间,而不需要算完后与 \(k\) 作比较来决定下一个二分的数。空间复杂度 \(O(n \log n)\)。
后者就是归并树,二分、拆分、再二分,一共三个 \(\log\)。
归并树的时间复杂度较高,其原因在于线段树没有维护“值域”的划分信息,需要经过两重二分才能与 \(k\) 进行比较。
线段树对值域的划分代替了对答案的二分。
——李煜东
说句闲话,多次二分答案还能从单次推广,这就是整体二分。
如果从数值考虑,此时最重要的是把仅包含下标区间的值域线段树弄出来。
一种方法是对值域对应的线段树的下标维度做前缀和,这就是主席树方法。单点修改,要在主席树上改 \(O(n)\) 个节点。时间复杂度 \(O(q \log n)\),空间复杂度 \(O(n \log n)\),算是静态问题的最好解法。
一种解决方法是。在值域上建线段树,每个节点存储该值域范围对应的下标区间,在下标区间上直接找 \([l,r]\) 范围内有几个数字。这样子做会多一个划分范围时的 \(\log\)。每个下标会出现 \(\log n\) 次,说明单次修改复杂度是 \(O(\log n)\)。单次询问复杂度 \(O(\log^2 n)\),空间复杂度 \(O(n \log n)\)。
另一种方法是把下标区间拆成 \(\log\) 个区间。开 \(n\) 棵值域线段树,按照树状数组存储方式,第 \(i\) 棵线段树存下标区间 \((i-lowbit(i),i]\) 对应的值域线段树。\(\log\) 棵线段树的合并,就是我们需要的包含指定下标区间的值域线段树。\(\log\) 棵线段树,划分 \(\log\) 次,时间复杂度 \(O(n \log^2 n)\)。内层线段树需要动态开点,每个点出现在 \(\log\) 棵树中,每次带来 \(O(\log n)\) 的空间,所以空间复杂度 \(O(n \log^2 n)\)。这样看着似乎劣于直接做前缀和,但是因为每个点只被包含在 \(O(\log n)\) 棵线段树里,单点修改的复杂度是 \(O(\log ^2 n)\)。
想想树状数组初始化每个节点只需要对 \(i+lowbit(i)\) 贡献,可以用可持久化线段树的方法把空间优化到 \(O(n \log n)\)。这个好像被叫做带修主席树,但感觉思想和主席树差好多的样子。
(可持久化再加上带修……怎么也听着怪怪的样子……)
本文来自博客园,作者:purplevine,转载请注明原文链接:https://www.cnblogs.com/purplevine/p/16703156.html