LA 问题的若干种解法

看了眼 P5903 的题解区,方法还是挺多的,那我就浅浅的总结一下。


Algorithm 1

最简单的,直接往他的父亲节点跳,单次询问复杂度 \(\mathrm O(n)\),总复杂度为 \(\mathrm O(qn)\)。这是最基础的写法。

Algorithm 2

我们用倍增的思想来做,这个也很简单不多讲,单次的复杂度为 \(\mathrm O(\log n)\),总复杂度为 \(\mathrm O(q \log n)\)

Algorithm 3

我们可以考虑使用重链剖分。如果这个链的顶端没有跳过的话,就直接跳到链顶的父亲,否则直接推就可以了,因为 \(dfs\) 序是连续的。单次复杂度也是 \(\mathrm O(\log n)\),总复杂度为 \(\mathrm O(q \log n)\)

看似复杂度是一样的,但是速度比 Algorithm 2 要快许多,此时已经可以通过了。

Algorithm 4

我们考虑如何优化 Algorithm 3。

我们发现我们需要跳到 \(k\) 级祖先的重链,然后我们就可以 \(\mathrm O(1)\) 知道祖先的位置了。然而跳链过程是 \(\mathrm O(\log n)\),瓶颈就这这里。我们考虑如何更快地找到这条链。

我们发现一个点上面至多有 \(\log n\) 条链,那么我们直接二分这个点锁在的重链的位置即可。单次的复杂度为 \(\mathrm O(\log \log n)\)。由于瓶颈在前面了,所以总复杂度不变。

Algorithm 5

我们依然考虑如何优化 Algorithm 3。

我们可以一次跳 \(\sqrt \log n\) 个祖先,这样子我们就可以一次多跳几个祖先了。单次复杂度为 \(\mathrm O(\sqrt \log n)\),总复杂度为 \(\mathrm O(q \sqrt \log n)\)

Algorithm 6

这题的正解:长链剖分。

比较麻烦。先按照节点深度树剖。然后利用倍增数组跳到 \(2^x\),剩余的距离差为 \(k_1\),这里 \(2^x > k_1\)。然后由于长链的长度 \(> k_1\),那么因此可以先将 \(x\) 跳到 \(x\) 所在链的顶点。

若之后剩下的级数为正,则利用向上的数组求出答案,否则利用向下的数组求出答案。单次时间复杂度 \(\mathrm O(1)\),但是瓶颈在前面,复杂度为 \(\mathrm O(n \log n)\)

Algorithm 7

这题还有一个比较科技的做法-----用 \(\pm 1 \ \text{FS(Find Smaller)}\) 解决 \(\text{LA}\) 问题。

我们发现 \(x\) 的深度为 \(d\) 的祖先是他后面的第一的深度 \(\le d\) 的节点。那么这个问题就是一个 \(\text{FS}\) 问题了。

然后欧拉序具有 \(\pm 1\) 的性质。那么这个问题就变成了 \(\pm 1 \ \text{FS(Find Smaller)}\) 问题,这个问题有良好的性质。可以支持在线常数查询。

定义\(\text{ctz}(z)\) 表示 \(\max_{k = 0} 2^k \mid x\)

定义\(\text{lbt}(i,j)\) 表示 \(i\)\(j\) 之间 \(\text{ctz}\) 最大的数。

引理:对于任意的 \(i,j\),都有 \(j - i + 1 \le 2^{\text{lbt(i,j) + 1}}\)

假设我们现在给出的数组为 \(a_{0,1,\cdots \cdots n - 1}\)。然后我们定义 \(f(i) = 3 \times 2^{\text{ctz(i)}},f(0) = n\)。然后我们定义 \(B_{i,1,2 \cdots f(i)}\),其中 \(B_{i,j} = \text{Fz}(i,a_i - j)\)

然后当我们查询 \(i\) 后面第一个大于 \(x\) 的数的位置时,答案如下:

  • \(a_i \le x\),答案为 \(i\)

  • 否则

    • \(d = a_i - x\),若 \(d \le f(i)\),那么答案为 \(B_{i,d}\)
    • 否则,我们令 \(d = lbt(i - d + 1,i)\),那么答案为 \(B_{k,a_k - x}\)

那么问题就变成了如何在 \(\mathrm O(n \log n)\) 预处理出 \(B\) 数组。当然处理是简单的,我们只需要倒着扫一遍,然后用一个桶记录一个数最前的出现位置。然后最终的复杂度就是 \(O(n \log n)\)

Algorithm 8

最高贵的线性做法!!!!!是 Algorithm 7 的进阶版本,在 Algorithm 7 当中定义的所有东西都在这里适用。

我们先定义 \(\text{Fs}(i,x)\)\(i\) 后面第一个 \(\le x\) 的数的位置。

考虑分块,我们取 \(b = \lfloor \frac{\log n}{2} \rfloor\)。然后我们先考虑块间的贡献。

对于每一个块,我们维护一下两个信息:

  • \(N_{i,1 \sim 2b}\),其中 \(N_{i,j} = \text{Fs}(i \times b,a_{i \times b} - j)\)

  • \(F_{i,1 \sim f(i)}\),其中 \(F_{i,j} = \lfloor \frac{\text{Fs}(ib,a_{ib}-jb)}{b} \rfloor\)

注意到如果 \(F_{i,j} = k\),那么 \(a_{ib} - jb \le a_{kb} < a_{ib} - (j - 1)b\)。这个式子可以用 \(\pm 1\) 性质证明。

然后我们考虑如何查询 \(\text{Fs}(ib + j,x)\)

  • \(x \ge a_{ib + j}\) 返回 \(ib + j\)
  • \(j > 0\)
    • 通过块内查询,若答案在块内,则直接返回;
    • 若答案不在块内,那么返回 \(\text{Fs}((i + 1)b,x)\)
  • \(j=0\)
    • \(x \ge a_{ib} - jb\),返回 \(N_{i,a_{ib} - x}\)
    • \(d = \lfloor \frac{a_{ib} - x}{b} \rfloor\)
      • \(d \le f(i)\),那么返回 \(\text{Fs}(F_{i,d}b,x)\)
      • 否则返回 \(\text{Fs}(\text{lbt}(i - d + 1,i)b,x)\)

然后我们发现因为:

\[a_{kb} < a_{ib} - (d - 1)b < x + 2b \]

所以所有的情况,都会变成 \(j = 0\) 的第一种情况。

然后我们考虑块内怎么做。

接下来是利用位掩码的方法,一般块长取 \(w\)

\[m(i,j)=\begin{cases}1\\ \ \ \ \ a_j < \min\{a_i,\dots,a_{j-1}\}\\0\\ \ \ \ \ mathrm{otherwise}.\end{cases} \]

那么,\(m(ib+j,ib+j+1),m(ib+j,ib+j+2)\dots m(ib+j,ib+(b-1))\) 中第 \(a_{ib+j}-x\)\(1\) 的位置就对应 \(\mathrm{Fs}(ib+j,x)\),如果不存在这样的位置,则说明答案不在块内。

我们将 \(m(ib+j,ib+j+1),m(ib+j,ib+j+2)\dots m(ib+j,ib+(b-1))\) 压入一个数 \(m_{ib+j}\) 内,查询即为查找第 \(k\) 个为 \(1\) 的二进制位位置,这一操作可以进行一些预处理后 \(O(1)\) 实现。

那么我们就获得了 \(\mathrm O(n) - O(1)\) 的写法了。

posted @ 2024-08-03 10:31  sqrtqwq  阅读(1)  评论(0编辑  收藏  举报