0 例题引入
给定一个有 nnn 个节点的树,然后有 qqq 次询问,每次询问两个节点 u,vu, vu,v 的 LCA\rm{LCA}LCA 。
1≤n≤3×105,1≤q≤1071 \leq n \leq 3 \times 10^5, 1 \leq q \leq 10^71≤n≤3×105,1≤q≤107
1 讲解
可以发现,倍增和树剖都会 TLE\rm{TLE}TLE ,因为每次询问都是 O(log2n)O(\log_2 n)O(log2n) 。
1.1 欧拉序
1.1.1 构造方法
实际上是一个比较特殊的 dfs\rm{dfs}dfs 序。
区别于普通的 dfs\rm{dfs}dfs 序,除了进入某个节点的时候加入一次序列,回溯到某个节点也会加入到序列,在这里写出伪代码(不想写C++)。
Func dfs(u) A.append(u) bgu=∣A∣ For v:=son(u) dfs(v) A.append(u)
\begin{aligned}
&\mathrm{Func}~dfs(u)\\
&\ \ \ \ \ \ \ \ A.\mathrm{append}(u)\\
&\ \ \ \ \ \ \ \ bg_u=|A|\\
&\ \ \ \ \ \ \ \ \mathrm{For}~v:=son(u)\\
&\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ dfs(v)\\
&\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ A.\mathrm{append}(u)
\end{aligned}
Func dfs(u) A.append(u) bgu=∣A∣ For v:=son(u) dfs(v) A.append(u)
其中 A.append(u)A.\mathrm{append}(u)A.append(u) 就是向序列末尾添入一个节点 uuu 。bgubg_ubgu 表示节点 uuu 在序列 AAA 中首次出现的位置。
1.1.2 性质
可以发现,对于任意一个节点 uuu, 其在序列中出现的次数是其儿子个数加一,那么通过简单的计算可以得到第一条性质:
∣A∣=2n
|A| = 2n
∣A∣=2n
另一个比较显然但是很重要的性质:
∀i∈[1,2n),depAi−depAi+1=±1
\forall i\in [1,2n),dep_{A_i}-dep_{A_{i+1}}=\pm1
∀i∈[1,2n),depAi−depAi+1=±1
还有一个在本文用到的性质:
对于两个节点 u,v(bgu≤bgv)u, v (bg_u \leq bg_v)u,v(bgu≤bgv),他们的 LCA\rm{LCA}LCA 一定是 AAA 的区间 [bgu,bgv][bg_u, bg_v][bgu,bgv] 中 depdepdep 最小的。正确性比较显然。
1.2 主要过程和程序实现
可以首先构建一个 ST\rm{ST}ST 表,记录下大小为 222 的幂的区间的 depdepdep 最小的 AiA_iAi ,然后在每次询问的时候找区间 [bgu,bgv][bg_u, bg_v][bgu,bgv] 中最小的 depdepdep 所在的位置。(代码可能有错)
int A[maxn << 1], A_sz, bg[maxn], dep[maxn];
void dfs(int u, int fa) {
A[++A_sz] = u, bg[u] = A_sz, dep[u] = dep[fa] + 1;
for (int i = hd[u]; i; i = E[i].nex) {
int v = E[i].v;
if (v == fa) continue;
dfs(v, u), A[++A_sz] = u;
}
}
int B[maxn << 1][maxlg2n], lg2[maxn << 1];
void init_st() {
for (int i = 1; i <= (n << 1); i++) B[i][0] = A[i];
for (int i = 2; i <= (n << 1); i++) lg2[i] = lg2[i >> 1] + 1;
for (int i = 1; i <= lg2[n << 1]; i++)
for (int j = 1; j + (1 << i) - 1 <= (n << 1); j++)
if (dep[B[i][j - 1]] < dep[B[i + (1 << (j - 1))][j - 1]])
B[i][j] = B[i][j - 1];
else B[i][j] = B[i + (1 << (j - 1))][j - 1];
}
int getlca(int u, int v) {
if (bg[u] > bg[v]) swap(u, v);
int lg = lg2[bg[v] - bg[u] + 1];
if (dep[B[bg[u]][lg]] < dep[B[bg[v] - (1 << lg) + 1][lg]])
return B[bg[u]][lg];
else return B[bg[v] - (1 << lg) + 1][lg];
}
可以发现,初始化的时间复杂度为 O(nlog2n)O(n\log_2 n)O(nlog2n),求一次 LCA\rm{LCA}LCA 的时间复杂度是 O(1)O(1)O(1) 的。因此总的时间复杂度是O(nlog2n+q)O(n \log_2 n + q)O(nlog2n+q) 的。