📅  最后修改于: 2023-12-03 15:17:17.562000             🧑  作者: Mango
Lary树是一种基于倍增的树形结构,它是一颗有 $n$ 个节点的有根树,节点编号为 $1$ 到 $n$。 它可以用 $nlogn$ 的时间预处理后支持 $O(1)$ 的最近公共祖先 (LCA) 查询。它与经典的tarjan离线算法相比,在常数上有显著的优势,实现较为简单。
假设有一颗树 $T$,我们要建立它的Lary树 $L_T$。Lary树是一颗新树 $L_T$,每个节点对应原树 $T$ 中的一条边。如果节点 $u$ 对应的边是 $(x,y)$,那么 $u$ 的父亲为将 $(x,y)$ 删去之后 $x$ 的LCA对应的节点。其中,我们约定 $LCA(x,x)=x$。
构建Lary树的过程可以用倍增的方式实现,具体可以参考LCA算法中的倍增实现。下面是Lary树的一颗样例:
LCA算法可以在 $\logn$ 的时间内找到两个节点的最近公共祖先。而Lary树可以支持 $O(1)$ 的查询,这是如何实现的呢?
我们设 $d(u)$ 表示节点 $u$ 的深度,$p_0(u)$ 表示节点 $u$ 的父亲,$f(u)$ 表示节点 $u$ 在Lary树中的父亲。那么,在Lary树中,节点 $u$ 和它的父亲 $f(u)$ 的深度必须满足以下三种情况之一:
这是为什么呢?根据Lary树的构造方式,节点 $u$ 在Lary树中对应的边 $(x,y)$ 的LCA是 $LCA(x,y)$。记 $d_x=depth[x]$,$d_y=depth[y]$。那么有以下情况:
由于Lary树上每个节点的深度相差至多为 $4$,所以情况 $3$ 对应的深度差是至多 $2$ 的,因此,满足情况 $3$ 的节点 $u$ 在Lary树中的父亲 $f(u)$ 必须满足 $d(f(u))=d(u)-1$。对于情况 $1$ 和情况 $2$,$f(u)$ 必须满足 $d(f(u))=d(u)-2$ 或者 $d(f(u))=d(u)-4$。
这样,在Lary树上查询两个节点的LCA,我们只需要将它们一直想上找,直到它们的深度相同,并且它们的LCA对应的边满足上面这三种情况即可。这个查询操作的时间复杂度是 $O(1)$ 的。
下面是Lary树的求LCA操作的C++代码:
int LCA(int u, int v) {
while (1) {
if (depth[u] < depth[v])
std::swap(u, v);
if (depth[f[u]] < depth[v])
break;
u = f[u];
}
while (depth[u] != depth[v])
u = f[u];
while (u != v) {
u = f[u];
v = f[v];
}
return u;
}
Lary树是一种简单高效的数据结构,它可以用 $O(nlogn)$ 的时间构造,支持 $O(1)$ 的求最近公共祖先等操作。它比离线算法在常数上有所优越,因此,在实际使用中,可以根据具体情况选用不同的算法。