log 插入 O(1) 查相对排名
一个几乎没有任何应用场景但是我很喜欢的 trick
一个序列,支持 \(O(\log)\) 插入,\(O(1)\) 查询两个数的相对排名。
我们考虑对序列重编号。一开始序列是空的,然后插入的第一个数将其编号为 \(0\),插入的第二个数将其编号为 \(2^{64}\)。之后插入的每个数,将其编号为它前驱后继的平均数。
显然这样比较排名就变成比较编号了。
但是有一个问题是,如果在一个缝隙里插入了 \(\log V\) 以上个数,编号就会变成实数,就直接爆掉了。
然后一个天才的想法是使用替罪羊树。替罪羊树会重构保证结点深度严格小于设定的阈值,于是直接维护就对了。
例题:【qoj7203 Four】
定义两个四元组相等当且仅当存在一个位置对应相等。
有一个四元组序列 \(A\),初始为空。接下来有 \(n\) 次操作:
-
给定一个位置和一个四元组,在给定位置插入。
-
给定一个四元组,查询有多少个位置,在那里插入后左右两边都有和插入四元组不相等的四元组。
强制在线。
\(n\le 10^6\),四元组每个数值域 \([0,16)\),4s。
首先这个题非常简单,因为题目本身性质很强所以比赛的时候只需要想到:考虑在左右分别维护一些四元组,使得无论你查询什么四元组,都能在维护的部分里找到和它不相等的(找不到说明全相等),同时直觉上来说需要维护的个数不多。于是就有了一个做法:
-
先保留第一个四元组。
-
然后向后找到第一个和它的 \(a\) 不相等的四元组,保留。
-
再向后找到和 1 的 \(a\) 不相等、和 2 的 \(b\) 不相等的四元组,保留。
-
再向后找 "和 1 的 \(a\) 不相等、和 2 的 \(b\) 不相等、和 3 的 \(c\) 不相等" 的四元组,保留。
-
再向后找 "和 1 的 \(a\) 不相等、和 2 的 \(b\) 不相等、和 3 的 \(c\) 不相等、和 4 的 \(d\) 不相等" 的四元组,保留。
在保留这五个四元组之后我们发现可以 check 掉所有:和 1 不相等的、和 1 只在 \(a\) 维度上相等的、和 1 的 \(a\) 相等且和 2 的 \(b\) 相等的、…… 这些四元组。
现在要把 check 范围覆盖所有四元组,我们就把上面 \(a,b,c,d\) 的顺序遍历 \(4!\) 种即可。正确性和复杂度都是显然的。
当然还是要介绍一下 trick 的应用。
先考虑离线的做法。
不难通过平衡树得到每个插入的元素在最终序列中的位置,我们设一个元素的权值是它的最终位置。查询时就只需计算满足条件的元素最小值和最大权值,再用 BIT 查一下它们中间有多少个数已插入即可。
以计算最小权值为例,接下来有两种思路:
- 容斥。在 \(2^4 = 16\) 棵线段树上并行进行线段树二分即可,复杂度 \(O(16n \log n)\)。然而这个做法很难拓展到在线。
- 类似于二进制分组的思路。为了方便将 \(a, b, c, d\) 都异或上 \(15\),变成要求 \(a, a'\) 至少有一个 bit 相等;\(b, c, d\) 同理。由于只关心最小值,可以枚举 \(a, b, c, d\) 分别是在哪一个 bit 上可以相等,设 \(F_{X,Y}\) 是选择 bit 的方式对应状态为 \(X\) 时这些 bit 的状态为 \(Y\) 的所有元素的最小权值,那么插入操作相当于更新 \(4^4 = 256\) 个 \(F\),查询操作相当于查 \(256\) 个 \(F\) 的 min。直接更新即可,复杂度 \(O(4^4n)\),可以轻松通过本题范围。
再来思考在线,我们延续上面所述的第二种思路。此时我们无法得到每个元素的权值。那我们重新设元素的权值为它在当前序列中的下标。考虑插入操作对 \(F\) 的影响,如果插入的位置是 \(k\),那么就有:\(< k\) 的数不变,\(\ge k\) 的数需要加 1。由于此时所有的 \(F\) 都有可能被更新,共需要更新 \(8^4 = 4096\) 个数,所以是很劣的。于是重新定义 \(F\) 为:权值最小的元素的编号。由于插入操作不会影响其余元素的相对关系,所以就只用更新 \(256\) 个 \(F\)。但我们陷入了新的困难:怎么比较两个元素权值的大小关系?这是修改和查询都需要的。
现在我们的问题是,如何对序列进行以下两种操作:
- 插入元素。
- 对两个元素的位置进行比较(必须 \(O(1)\))。
这就是上面的 trick。当然如果不会这个 trick,根号重构也是可以的,不过是 \(O(n\sqrt n)\)。

浙公网安备 33010602011771号