【复健】LCA复健笔记

LCA 复健笔记

image

展开目录

什么是 LCA

最近公共祖先/最深公共祖先,顾名思义,两个点的公共祖先中离它们最近/深度最大的那个。

怎么求 LCA

这里使用倍增优化算法,因为之前看不懂所以我觉得应该补一下(现在也看不懂。)。

暴力求 LCA

不停向上搜索,直到两点合为一点。

展开代码
int LCA(int x, int y) {
    if(d[x] < d[y]) swap(x, y); // 保持 x 的深度大于等于 y
    while(d[x] != d[y]) x = F[x];
	while(x != y) x = F[x], y = F[y];
	return x;
}

倍增优化

显然,在大部分使用 LCA 的情景中,上面的暴力做法会超时。

倍增是一个经常会使用到的优化(比如 \(ST\) 表),核心思想就是把搜索的步长从 \(1\) 变到 \(2^k\).

f[i][j] 代表节点 \(i\) 向上搜索 \(2^j\) 层后到达的点。有递推式:

f[i][j] = f[f[i][j - 1]][j - 1]

根据数学原理,从第 \(i\) 个点向上搜 \(2^{j - 1}\) 层后再搜 \(2^{j - 1}\) 层相当于搜了 \(2^j\) 层。

于是我们得到了搜索的底层逻辑:

将深度较大的一方上移,直到该方深度小于另一方,接着将两点一同上移直到两点只合为一点。

展开代码
inline int lca(ll x, ll y) {
	if(d[x] < d[y]) swap(x, y);
	for(int i = LogN; i >= 0; --i) if(d[f[x][i]] >= d[y]) x = f[x][i];
	if(x == y) return x;
	for(int i = l[n]; i >= 0; --i) if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0];
}

接下来思考如何初始化。

一个点的深度是它父亲节点的深度 \(+1\).

此外,根据刚才的递推式也可以计算出 \(f\) 数组的值。递推上界到 \(\log{n}\) 就可以了。

于是有递归初始化。

展开代码
void init(ll u, ll fa) {
	d[u] = d[fa] + 1;
	for(int i = 1; i <= LogN; ++i) {
		if((1 << i) >= d[u]) break;
		f[u][i] = f[f[u][i - 1]][i - 1];
	}
	for(int i = head[u]; i; i = nxt[i]) {
		int v = to[i];
		if(v == fa) continue;
		f[v][0] = u;
		init(v, u);
	}
}

应用场景

你需要求两个(或多个)点的公共祖先,但值域很大。

不适合的应用场景

Type This

posted @ 2024-07-31 15:54  _Kiichi  阅读(22)  评论(0编辑  收藏  举报