洛谷-P3380/LibreOJ-106/BZOJ-3196 题解(树套树,2B 平衡树)

洛谷链接 vjudge链接

LibreOJ链接 vjudge链接

黑暗爆炸OJ链接 vjudge链接

new BZOJ链接 vjudge链接

题意简述

给定一个数列,支持以下操作:

  1. 查询 \(k\) 在区间内的排名(区间内比 \(k\) 小的数的个数 \(+1\)

  2. 查询区间内排名为 \(k\) 的值

  3. 修改某一位值上的数值

  4. 查询 \(k\) 在区间内的前驱(前驱定义为严格小于 \(k\),且最大的数,若不存在输出 -2147483647

  5. 查询 \(k\) 在区间内的后继(后继定义为严格大于 \(k\),且最小的数,若不存在输出 2147483647

思路

可以用线段树套平衡树来做。

先为这个序列建一棵线段树,线段树的每个结点都是一棵平衡树,这棵平衡树里面包含这个结点所代表的区间中的所有数。本人选用的是常数更小的 Treap 树。

那么便可以完成以下操作:

  1. 查询 \(k\) 在区间内的排名(区间内比 \(k\) 小的数的个数 \(+1\)

先在线段树上找到所有需要查找的结点,用平衡树的查询排名操作分别查询,把所有的答案加起来即可。

时间复杂度为 \(\mathcal{O}(\log^2 N)\)

  1. 查询区间内排名为 \(k\) 的值

这个不能直接查询,我们可以二分答案,每次用操作 \(1\) 查询二分到的答案,再根据答案与 \(k\) 的关系更新区间。具体做法见代码中。

时间复杂度为 \(\mathcal{O}(\log^3 N)\)

  1. 修改某一位值上的数值

在线段树上找到所有包含这个数的结点,然后删除这个数,再插入新的数。

时间复杂度为 \(\mathcal{O}(\log^2 N)\)

  1. 查询 \(k\) 在区间内的前驱(前驱定义为严格小于 \(k\),且最大的数,若不存在输出 -2147483647

在线段树的每个需要查找的结点上找到前驱,然后取最大值。要注意不存在的情况,方法是设一个 \(FAIL\),在查找第 \(k\) 小数时如果 \(k\) 不合法就返回 \(FAIL\)。其值要取一个不等于数列中任何数的数,如 \(10^9\)

时间复杂度为 \(\mathcal{O}(\log^2 N)\)

  1. 查询 \(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)\)

代码

posted @ 2024-02-19 21:13  lrx139  阅读(18)  评论(0编辑  收藏  举报