最近公共祖先学习笔记
作者:互联网
概念
在一棵有根树上,指定点集的最近公共祖先(即 LCA ),就是这些节点的祖先集合的并集中离根最远的点
实现
暴力
先对树进行一次深搜,预处理出每个节点的父亲与深度
对于每一次查询,我们先让深度较大的点向上跳,直到两点深度相同为止
接下来让这两个点一起向上跳,直到这两点相遇为止,此时该节点就是这两个点的 LCA
倍增优化暴力
我们令 \(dp_{i,j}\) 表示节点 \(i\) 的第 \(2^j\) 级祖先(即 \(i\) 要向上跳 \(2^j\) 次到达 \(dp_{i,j}\) )
显然易得式子:
dp[i][j] = dp[dp[i][j - 1]][j - 1];
我们先放上深搜预处理的代码
inline void dfs(int u, int fa) {
dp[u][0] = fa, dep[u] = dep[fa] + 1;
for (int i = 0; i < LOG[dep[u]]; ++i)
dp[u][i + 1] = dp[dp[u][i]][i];
for (int i = 0, v; i < edge[u].size(); ++i) {
v=edge[u][i];
if (v != fa)
dfs(v, u);
}
}
我们考虑暴力的第一步:让深度较大的点向上跳,直到两点深度相同为止
考虑对两点的深度差进行二进制拆分,利用倍增数组向上跳
接着考虑暴力第二步:让这两个点一起向上跳,直到这两点相遇为止
我们利用倍增的思想,从大到小枚举两点的 \(2^k\) 级祖先,判断其是否相等,找出最近公共祖先的儿子,最后返回其父亲即可
查询 LCA 代码:
inline int LCA(int x, int y) {
if (dep[x] < dep[y])
swap(x, y);
for (int i = 0, h = dep[x] - dep[y]; h; ++i, h >>= 1)
if (h & 1)
x = dp[x][i];
if (x == y)
return x;
for (int i = LOG[dep[x]]; ~i; --i)
if (dp[x][i] != dp[y][i])
x = dp[x][i], y = dp[y][i];
return dp[x][0];
}
用欧拉序列转化为 RMQ 问题
前置芝士:欧拉序列
对一棵树进行深搜,无论是访问还是回溯,每次到达一个结点都将编号记录下来,可以得到一个长度为 \(2n-1\) 的序列,这个序列被称作这棵树的欧拉序列 \(F\)
记点 \(u\) 在欧拉序列中第一次出现的位置为 \(pos_u\)
不难发现,在欧拉序列中 \(u \to v\) 的路径会经过 \(LCA(u,v)\) 且不会经过 \(LCA(u,v)\) 的各级祖先,所以得出一下结论:
\[LCA(u,v) = \min_{i = pos_u}^{pos_v} F_i \]直接用 ST 表解决即可
inline void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
dfn[++ cnt] = u,pos[u] = cnt;
for (int i = 0, v; i < edge[u].size(); ++i) {
v = edge[u][i];
if (v == fa)
continue;
dfs(v, u);
dfn[++ cnt] = u;
}
return ;
}
inline void init() {
LOG[0] = -1;
for (int i = 1; i <= cnt; ++ i)
LOG[i] = LOG[i >> 1] + 1;
for (int i = 1; i <= cnt; ++ i)
dp[i][0] = dfn[i];
for (int j = 1; j <= LOG[cnt]; ++ j)
for (int i = 1; i <= cnt - (1 << j) + 1; ++ i)
if (dep[dp[i][j - 1]] < dep[dp[i + (1 << (j - 1))][j - 1]])
dp[i][j] = dp[i][j - 1];
else
dp[i][j] = dp[i + (1 << (j - 1))][j - 1];
}
inline int query(int l, int r) {
int k = LOG[r - l + 1];
return dep[dp[l][k]] < dep[dp[r - (1 << k) + 1][k]] ? dp[l][k] : dp[r - (1 << k) + 1][k];
}
inline int LCA(int x, int y) {
int l = pos[x], r = pos[y];
if (l > r)
swap(l, r);
return query(l, r);
}
树链剖分
将两个点向上跳到同一条重链上,深度较浅的节点就是它们的 LCA
标签:dep,fa,祖先,LCA,++,笔记,int,公共,dp 来源: https://www.cnblogs.com/wshcl/p/LCA.html