欧拉序求LCA

0 例题引入

给定一个有 n n n 个节点的树,然后有 q q q 次询问,每次询问两个节点 u , v u, v u,v L C A \rm{LCA} LCA

1 ≤ n ≤ 3 × 1 0 5 , 1 ≤ q ≤ 1 0 7 1 \leq n \leq 3 \times 10^5, 1 \leq q \leq 10^7 1n3×105,1q107

1 讲解

可以发现,倍增和树剖都会 T L E \rm{TLE} TLE ,因为每次询问都是 O ( log ⁡ 2 n ) O(\log_2 n) O(log2n)

1.1 欧拉序

1.1.1 构造方法

实际上是一个比较特殊的 d f s \rm{dfs} dfs 序。

区别于普通的 d f s \rm{dfs} dfs 序,除了进入某个节点的时候加入一次序列,回溯到某个节点也会加入到序列,在这里写出伪代码(不想写C++)。
F u n c   d f s ( u )          A . a p p e n d ( u )          b g u = ∣ A ∣          F o r   v : = s o n ( u )                  d f s ( v )                  A . a p p e n d ( 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 . a p p e n d ( u ) A.\mathrm{append}(u) A.append(u) 就是向序列末尾添入一个节点 u u u b g u bg_u bgu 表示节点 u u u 在序列 A A A 中首次出现的位置。

1.1.2 性质

可以发现,对于任意一个节点 u u u, 其在序列中出现的次数是其儿子个数加一,那么通过简单的计算可以得到第一条性质:
∣ A ∣ = 2 n |A| = 2n A=2n
另一个比较显然但是很重要的性质:
∀ i ∈ [ 1 , 2 n ) , d e p A i − d e p A i + 1 = ± 1 \forall i\in [1,2n),dep_{A_i}-dep_{A_{i+1}}=\pm1 i[1,2n),depAidepAi+1=±1
还有一个在本文用到的性质:

对于两个节点 u , v ( b g u ≤ b g v ) u, v (bg_u \leq bg_v) u,v(bgubgv),他们的 L C A \rm{LCA} LCA 一定是 A A A 的区间 [ b g u , b g v ] [bg_u, bg_v] [bgu,bgv] d e p dep dep 最小的。正确性比较显然。

1.2 主要过程和程序实现

可以首先构建一个 S T \rm{ST} ST 表,记录下大小为 2 2 2 的幂的区间的 d e p dep dep 最小的 A i A_i Ai ,然后在每次询问的时候找区间 [ b g u , b g v ] [bg_u, bg_v] [bgu,bgv] 中最小的 d e p dep dep 所在的位置。(代码可能有错)

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 ( n log ⁡ 2 n ) O(n\log_2 n) O(nlog2n),求一次 L C A \rm{LCA} LCA 的时间复杂度是 O ( 1 ) O(1) O(1) 的。因此总的时间复杂度是 O ( n log ⁡ 2 n + q ) O(n \log_2 n + q) O(nlog2n+q) 的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值