【模版】最近公共祖先LCA(倍增)


最近公共祖先LCA(倍增)


给定一棵 以 s s s 为根节点,共有 n n n 个点的树。
m m m 次查询 任意两点 u , v u ,v u,v 的最近公共祖先


一、前置知识点
1.邻接链表 存图
2.倍增原理 ( 2 n 2^n 2n 的预处理应用 )
二、算法流程
1.预处理一:
s s s 为根节点进行一遍dfs。
处理出:孩子节点 i i i 的深度( d e e p [ i ] deep[i] deep[i]) : d e e p [ e [ i ] . t o ] = d e e p [ x ] + 1 deep[e[i].to] = deep[x] + 1 deep[e[i].to]=deep[x]+1
处理出:孩子节点向上跳 2 0 2^0 20所到达的点(即为本身)。: f [ e [ i ] . t o ] [ 0 ] = x f[ e[i].to ][0]=x f[e[i].to][0]=x
f [ i ] [ j ] f[i][j] f[i][j] 数组表示点 i i i 向上跳 2 j 2^j 2j 所到达的点。
2.预处理二:
对 数组 f [ i ] [ j ] f[i][j] f[i][j] 进行倍增的递推处理。
j = = 0 j == 0 j==0 时,情况已经在 dfs 中被预处理出来。
考虑当 j > 0 j > 0 j>0 时,点 i i i 向上跳 2 j 2^j 2j所到达的点,即为 点 i i i 向上跳 2 j − 1 2^{j-1} 2j1所到达的点 再向上跳 2 j − 1 2^{j-1} 2j1所到达的点。 因为 2 j − 1 + 2 j − 1 = 2 j 2^{j-1} + 2^{j-1} = 2^j 2j1+2j1=2j
所以外层枚举 j j j ,内层枚举点 i i i
易得状态转移方程 f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ] f[i][j] = f[ f[i][j-1] ][j-1] f[i][j]=f[f[i][j1]][j1]
3.查询:
对于要查询的点 u , v u,v u,v ,先按深度取深度较深的点,设为点 v v v
首先算出两点的深度差,设为 d d d
枚举 次幂 i i i 将点 v v v 向上跳至 与 v v v 同深度。
若此时 u , v u,v u,v 相等,则返回 u u u
否则 次幂 从高次 向 低次枚举 i i i 。若 u , v u,v u,v 向上跳 2 i 2^i 2i 所到达的点不相同,则向上跳,然后继续枚举,直到次幂变为0。

三、例题
最近公共祖先LCA模版
货车运输

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int n,m,s,depth;
int head[1010100],deep[1010100];
int f[505050][21];
struct next_list
{
	int to,nxt;
}e[1010100];
void add_edge(int u, int v)
{
	e[++head[0]].to = v;
	e[head[0]].nxt = head[u];
	head[u] = head[0];
}
void dfs(int x)
{
	for(int i = head[x]; i; i = e[i].nxt)
	{
		if(deep[e[i].to])	continue;
		f[e[i].to][0] = x;
		deep[e[i].to] = deep[x]+1;
		dfs(e[i].to);
	} 
}

int query_lca(int u, int v)
{
	if(deep[u] > deep[v])	swap(u,v);
	int d = deep[v] - deep[u];
	for(int i = 0; i <= depth; i++)	if(d & (1<<i))	v = f[v][i];
	if(u == v)	return v;
	for(int i = depth; i >= 0; i--)	if(f[v][i] != f[u][i])	v = f[v][i], u = f[u][i];
	return f[v][0];
}

int main()
{
	scanf("%d%d%d",&n,&m,&s);
	depth = log(n) / log(2) + 1;
	for(int i = 1; i < n; i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v), add_edge(v,u); 
	}
	deep[s] = 1;
	dfs(s);
	for(int j = 1; j <= depth; j++)
		for(int i = 1; i <= n; i++)
			f[i][j] = f[f[i][j-1]][j-1];
	for(int i = 1; i <= m; i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		printf("%d\n",query_lca(u,v));
	}
	return 0;
}
<think>好的,用户想了解最近公共祖先LCA)算法的详细解释和C++实现模板。首先,我需要回忆LCA的基本概念和常见算法。用户提到的引用中提到了倍增算法,所以可能需要重点讲解这个。 首先,LCA的定义是两个节点在树中的最低共同祖先。常见的算法有朴素算法、倍增法、Tarjan离线算法和RMQ在线算法。根据引用内容,用户可能更关注倍增算法,因为引用里提到倍增是朴素算法的改进,使用预处理f数组来减少跳跃次数。 接下来,我需要解释倍增算法的原理。预处理f数组,其中f[x][i]表示节点x的第2^i个祖先。这可以通过DFS预处理得到,递推式是f[u][i] = f[f[u][i-1]][i-1]。然后查询时,通过调整两个节点的深度,再一起向上跳,直到找到LCA。 然后,需要给出C++的实现模板。包括预处理深度和f数组的DFS函数,以及查询LCA的步骤。要注意代码的结构,比如使用邻接表存储树,处理节点深度,二进制跳跃等。 用户还可能关心时间复杂度。倍增算法预处理是O(n logn),每次查询是O(logn),这比朴素算法的O(n)查询要好。引用里提到二分优化成单log,可能指的是在调整深度时的优化步骤。 另外,需要检查是否有其他需要注意的点,比如树的结构是否为有根树,节点编号是否从1开始等。在代码实现中,通常假设树是有根的,根节点可能是1,需要根据具体情况调整。 可能还需要对比其他算法,比如Tarjan离线算法适用于多查询情况,但实现较复杂;而倍增法是在线算法,适合单次查询。用户的问题可能更倾向于在线算法的实现。 最后,生成相关问题时要围绕LCA的不同算法、优化方法、应用场景等,确保问题具有针对性,帮助用户进一步学习。</think>### 最近公共祖先LCA)算法详解与C++实现 最近公共祖先(Lowest Common Ancestor, LCA)是树结构中两个节点的最低层共同祖先节点。以下是基于倍增算法的高效实现方案: --- #### 一、算法核心思想 1. **倍增预处理** - 定义 $f[x][i]$ 表示节点 $x$ 的第 $2^i$ 个祖先 - 递推关系:$f[x][i] = f[f[x][i-1]][i-1]$(即通过两次 $2^{i-1}$ 级跳跃实现) - 使用DFS预处理所有节点的深度和祖先关系[^1] 2. **LCA查询步骤** - 调整两个节点到同一深度 - 同步向上跳跃,利用预处理结果快速定位公共祖先 --- #### 二、C++实现模板 ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 5e5 + 5; const int LOG = 20; vector<int> tree[MAXN]; int depth[MAXN], f[MAXN][LOG]; // 预处理深度和祖先关系 void dfs(int u, int fa) { depth[u] = depth[fa] + 1; f[u][0] = fa; for (int i = 1; (1 << i) <= depth[u]; ++i) f[u][i] = f[f[u][i-1]][i-1]; // 核心递推式 for (int v : tree[u]) if (v != fa) dfs(v, u); } // 查询LCA int lca(int u, int v) { if (depth[u] < depth[v]) swap(u, v); // 调整到同一深度 for (int i = LOG-1; i >= 0; --i) if (depth[u] - (1 << i) >= depth[v]) u = f[u][i]; if (u == v) return u; // 同步跳跃 for (int i = LOG-1; i >= 0; --i) { if (f[u][i] != f[v][i]) { u = f[u][i]; v = f[v][i]; } } return f[u][0]; } int main() { // 初始化树结构后调用dfs(root, 0) // 处理查询lca(u, v) return 0; } ``` --- #### 三、算法分析 | 操作 | 时间复杂度 | 空间复杂度 | |------------|------------|------------| | 预处理 | $O(n \log n)$ | $O(n \log n)$ | | 单次查询 | $O(\log n)$ | - | --- #### 四、关键优化点 1. **二进制拆分**:利用指数级跳跃减少查询次数[^2] 2. **深度对齐**:通过循环调整确保比较节点处于同一层 3. **跳跃终止条件**:当祖先不同时才执行跳跃,避免过度回溯 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值