洛谷-P3380/LibreOJ-106/BZOJ-3196 题解(树套树,2B 平衡树)
题意简述
给定一个数列,支持以下操作:
-
查询 \(k\) 在区间内的排名(区间内比 \(k\) 小的数的个数 \(+1\))
-
查询区间内排名为 \(k\) 的值
-
修改某一位值上的数值
-
查询 \(k\) 在区间内的前驱(前驱定义为严格小于 \(k\),且最大的数,若不存在输出
-2147483647
) -
查询 \(k\) 在区间内的后继(后继定义为严格大于 \(k\),且最小的数,若不存在输出
2147483647
)
思路
可以用线段树套平衡树来做。
先为这个序列建一棵线段树,线段树的每个结点都是一棵平衡树,这棵平衡树里面包含这个结点所代表的区间中的所有数。本人选用的是常数更小的 Treap 树。
那么便可以完成以下操作:
- 查询 \(k\) 在区间内的排名(区间内比 \(k\) 小的数的个数 \(+1\))
先在线段树上找到所有需要查找的结点,用平衡树的查询排名操作分别查询,把所有的答案加起来即可。
时间复杂度为 \(\mathcal{O}(\log^2 N)\)。
- 查询区间内排名为 \(k\) 的值
这个不能直接查询,我们可以二分答案,每次用操作 \(1\) 查询二分到的答案,再根据答案与 \(k\) 的关系更新区间。具体做法见代码中。
时间复杂度为 \(\mathcal{O}(\log^3 N)\)。
- 修改某一位值上的数值
在线段树上找到所有包含这个数的结点,然后删除这个数,再插入新的数。
时间复杂度为 \(\mathcal{O}(\log^2 N)\)。
- 查询 \(k\) 在区间内的前驱(前驱定义为严格小于 \(k\),且最大的数,若不存在输出
-2147483647
)
在线段树的每个需要查找的结点上找到前驱,然后取最大值。要注意不存在的情况,方法是设一个 \(FAIL\),在查找第 \(k\) 小数时如果 \(k\) 不合法就返回 \(FAIL\)。其值要取一个不等于数列中任何数的数,如 \(10^9\)。
时间复杂度为 \(\mathcal{O}(\log^2 N)\)。
- 查询 \(k\) 在区间内的后继(后继定义为严格大于 \(k\),且最小的数,若不存在输出
2147483647
)
方法与前驱类似。
因此总时间复杂度为 \(\mathcal{O}(M \log^3 N)\)。
那么空间复杂度怎样呢?
由于每个数会加入到 \(\log N\) 棵平衡树中,所以共需要的结点数为 \(N \log N\) 个。每次操作 \(3\) 会删除和新增 \(\log N\) 个结点,如果我们直接删除,那么共需要的结点数为 \(M \log N\)。因此空间复杂度为 \(\mathcal{O}((N+M) \log N)\)。
直接删除会浪费太多空间,于是我们可以用一个栈保存可以使用的结点,新增结点时从栈顶取,删除结点时再加入栈。这样空间复杂度就会降为 \(\mathcal{O}(N \log N)\)。