LCA (Tarjan&倍增)

本文深入讲解两种求解最近公共祖先(LCA)问题的算法:Tarjan算法和倍增算法。Tarjan算法结合并查集从叶子节点向上合并,而倍增算法通过二进制优化实现快速查找。附带详细代码及洛谷P3292题解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LCA_Tarjan

参考博客:https://www.cnblogs.com/JVxie/p/4854719.html

LCA的Tarjan写法需要结合并查集

从叶子节点往上并

int Find (int x) {
    return x == pre[x] ? x:pre[x] = Find(pre[x]);
}
void dfs(int x,int w,int fa) {
    d[x] = w;    //点x的深度
    //遍历与点x相连的点(除了已经访问过的和其父节点)
    for (int i = 0; i < v[x].size(); i++) {
        if (!vis[v[x][i]] && v[x][i] != fa) {
            dfs(v[x][i],w+1,x);
            pre[v[x][i]] = x;
            vis[v[x][i]] = 1;
        }
    }
    //访问所有和x有询问关系的e
    //如果e被访问过;
   //x,e的最近公共祖先为find(e);
}

 

LCA_倍增

  参考博客:https://www.cnblogs.com/zhouzhendong/p/7256007.html

  先写暴力写法:当x,y深度不同时先把深的调到和浅的同一深度再一起往前面跳找其最近公共祖先。

void dfs(int f,int u){
    fa[u]=f;
    d[u]=d[f]+1;
    for (int i=0;i<v[u].size();i++)
        if (v[u][i]!=f) dfs(u,v[u][i]);
}
int LCA(int a,int b){
    if (d[a]>d[b])
        swap(a,b);
    while (d[b]>d[a]) b=fa[b];
    while (a!=b) a=fa[a],b=fa[b];
    return a;
}

  显然,一个一个的跳太慢了,我们可以考虑通过二进制进行优化,也就是倍增。

void dfs(int f,int u) {
    fa[u][0] = f;
    dep[u] = dep[f] + 1;
    for (int i = 1; i <= 15; i++) {
        fa[u][i] = fa[fa[u][i-1]][i-1];
        //其他操作
    }
    for (int i = 0; i < v[u].size(); i++) {
        if (f == v[u][i]) continue;
        dfs(u,v[u][i]);
    }
}
void lca(int x,int y) {
    memset(ans,0,sizeof(ans));
    if (dep[x] < dep[y]) swap(x,y);
    for (int i = 15; i >= 0; i--) 
        if (dep[fa[x][i]] >= dep[y]) {
            //其他操作
            x = fa[x][i];
        }
    if (x == y) {
        return;
    }
    for (int i = 15; i >= 0; i--) 
        if (fa[x][i] != fa[y][i]) {
            //其他操作
            x = fa[x][i],y = fa[y][i];
        }
}

例题:洛谷P3292 [SCOI2016]幸运数字  | 题解

 

转载于:https://www.cnblogs.com/l999q/p/11291627.html

<think>首先,用户的问题是关于C++实现LCA(最低公共祖先)算法的倍增方法。我需要提供详细的解释和代码实现。 从系统指令中,我必须: - 使用$...$格式行内数学表达式。 - 使用$$...$$格式独立公式。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾添加引用标识。 - 结构清晰,帮助用户逐步解决问题,保证真实可靠。 参考引用: - 引用[1]是关于C++的LCA倍增法实现。 - 引用[2]解释了倍增法,并提供了ST表的代码。 - 引用[3]讨论了LCA的预处理。 - 引用[4]提供了LCA函数的具体实现。 用户意图是了解如何使用C++实现LCA倍增法,所以我需要基于这些引用构建回答。 关键点: - 倍增法是一种优化方法,将线性处理转化为对数级。 - LCA问题:找到树中两个节点的最低公共祖先。 - 实现步骤包括: 1. 预处理:计算每个节点的深度和倍增祖先表。 2. 查询:通过倍增法快速找到LCA。 从引用[4]中,LCA函数包括: - 调整深度:使两个节点在同一深度。 - 然后,使用倍增法找到LCA。 步骤分解: 1. **预处理**: - 计算每个节点的深度(depth)。 - 构建一个表(例如,parent[i][j]),表示节点i的第2^j个祖先。 - 这通常在DFS遍历树后完成。 2. **查询LCA**: - 如果节点a深度小于b,则交换。 - 将深度较大的节点向上移动,直到同一深度。 - 然后,同时向上移动a和b,直到找到LCA。 从引用[4]的代码: ```cpp int lca(int a, int b) { if (deep[a] < deep[b]) swap(a, b); for (int i = 0; (1 << i) <= deep[a]; i++); i--; for (int j = i; j >= 0; j--) if (deep[a] - (1 << j) >= deep[b]) a = p[a][j]; if (a == b) return a; for (int j = i; j >= 0; j--) if (p[a][j] != -1 && p[a][j] != p[b][j]) { a = p[a][j]; b = p[b][j]; } return p[a][0]; } ``` 在回答中,我需要: - 解释概念。 - 提供完整的C++代码示例。 - 确保数学表达式正确:例如,深度差、倍增原理。 - 生成相关问题。 结构回答: 1. **介绍LCA倍增法**:简要解释问题和算法。 2. **预处理步骤**:描述如何构建深度和祖先表。 3. **查询步骤**:详细解释倍增查询过程。 4. **完整代码实现**:提供基于引用的完整C++代码。 5. **生成相关问题**:在最后添加相关问题。 数学表达式: - 行内:如深度差 $ \text{depth}[a] - 2^j \geq \text{depth}[b] $。 - 独立公式:如倍增原理 $ \text{parent}[i][j] = \text{parent}[\text{parent}[i][j-1]][j-1] $。 使用中文回答。 最后,添加引用标识:在引用段落末尾加[^n],但引用是基于提供的文本,所以我会在解释时引用它们。 相关问题生成: - 如:如何优化LCA算法?其他实现方法?应用场景?</think>### C++实现LCA(最低公共祖先)倍增法详解 LCA(最低公共祖先)是树结构中两个节点的最近共同祖先节点。倍增法通过预处理每个节点的祖先信息,将查询时间复杂度优化到 $O(\log n)$,适用于静态树结构。下面我将逐步解释实现原理和C++代码。 #### 1. **倍增法核心思想** 倍增法利用二进制分解原理:任何整数可表示为 $2^0 + 2^1 + \cdots + 2^k$。预处理时,为每个节点存储其 $2^j$ 级祖先($j \geq 0$)。查询时,通过“跳步”方式(每次跳 $2^j$ 层)快速调整节点深度并找到LCA。时间复杂度: - 预处理:$O(n \log n)$ - 查询:$O(\log n)$ 数学上,祖先表定义如下: $$ \text{parent}[u][j] = \begin{cases} \text{直接父节点} & \text{if } j = 0 \\ \text{parent}[\text{parent}[u][j-1]][j-1] & \text{if } j > 0 \end{cases} $$ 这表示节点 $u$ 的第 $2^j$ 个祖先可通过递归查询得到[^4]。 #### 2. **实现步骤** ##### (1) 预处理:构建深度和祖先表 - **输入**:树的邻接表表示(节点从1开始编号)。 - **步骤**: 1. 通过DFS计算每个节点的深度 $\text{depth}[u]$。 2. 初始化祖先表 $\text{parent}[u][j]$,其中 $j$ 最大为 $\lfloor \log_2 n \rfloor$。 3. 递归填充祖先表:$\text{parent}[u][j] = \text{parent}[\text{parent}[u][j-1]][j-1]$。 ```cpp #include <iostream> #include <vector> #include <cmath> #include <algorithm> using namespace std; const int MAXN = 100010; // 最大节点数 const int MAXLOG = 20; // 最大对数级别: log2(MAXN) vector<int> tree[MAXN]; // 树的邻接表 int depth[MAXN]; // 节点深度 int parent[MAXN][MAXLOG]; // 祖先表 // DFS遍历树,初始化深度和祖先表 void dfs(int u, int p) { depth[u] = depth[p] + 1; parent[u][0] = p; for (int j = 1; j < MAXLOG; j++) { if (parent[u][j-1] != -1) { parent[u][j] = parent[parent[u][j-1]][j-1]; } else { parent[u][j] = -1; // 无效祖先标记 } } for (int v : tree[u]) { if (v != p) dfs(v, u); } } // 预处理入口 void preprocess(int root, int n) { depth[root] = 0; for (int i = 0; i < MAXLOG; i++) { parent[root][i] = -1; // 根节点无祖先 } for (int v : tree[root]) { dfs(v, root); } } ``` ##### (2) 查询LCA倍增跳步 - **输入**:两个节点 $a$ 和 $b$。 - **步骤**: 1. **深度对齐**:如果 $\text{depth}[a] < \text{depth}[b]$,交换 $a$ 和 $b$。然后将 $a$ 向上跳步,直到 $\text{depth}[a] = \text{depth}[b]$。跳步时,从最大步长 $2^j$ 开始尝试。 2. **查找LCA**:如果此时 $a = b$,则 $b$ 是LCA;否则,同时向上跳 $a$ 和 $b$,直到它们的祖先不同。最终,$\text{parent}[a][0]$ 是LCA。 ```cpp int lca(int a, int b) { // 确保a深度 ≥ b深度 if (depth[a] < depth[b]) swap(a, b); // 计算最大跳步指数 j: 满足 2^j <= depth[a] int j = 0; while ((1 << j) <= depth[a]) j++; j--; // 对齐深度:a向上跳,直到 depth[a] = depth[b] for (int k = j; k >= 0; k--) { if (depth[a] - (1 << k) >= depth[b]) { a = parent[a][k]; } } if (a == b) return a; // b是a的祖先 // 同时跳步a和b,找LCA for (int k = j; k >= 0; k--) { if (parent[a][k] != -1 && parent[a][k] != parent[b][k]) { a = parent[a][k]; b = parent[b][k]; } } return parent[a][0]; // 返回直接父节点作为LCA } ``` #### 3. **完整代码示例** 以下是一个完整的C++程序,包括树的构建、预处理和查询: ```cpp #include <iostream> #include <vector> #include <cmath> #include <algorithm> using namespace std; const int MAXN = 100010; const int MAXLOG = 20; vector<int> tree[MAXN]; int depth[MAXN], parent[MAXN][MAXLOG]; void dfs(int u, int p) { depth[u] = depth[p] + 1; parent[u][0] = p; for (int j = 1; j < MAXLOG; j++) { parent[u][j] = (parent[u][j-1] == -1) ? -1 : parent[parent[u][j-1]][j-1]; } for (int v : tree[u]) { if (v != p) dfs(v, u); } } void preprocess(int root, int n) { fill(depth, depth + n + 1, 0); for (int i = 0; i <= n; i++) { for (int j = 0; j < MAXLOG; j++) parent[i][j] = -1; } depth[root] = 0; for (int v : tree[root]) dfs(v, root); } int lca(int a, int b) { if (depth[a] < depth[b]) swap(a, b); int j = log2(depth[a]) + 1; for (int k = j; k >= 0; k--) { if (depth[a] - (1 << k) >= depth[b]) a = parent[a][k]; } if (a == b) return a; for (int k = j; k >= 0; k--) { if (parent[a][k] != -1 && parent[a][k] != parent[b][k]) { a = parent[a][k]; b = parent[b][k]; } } return parent[a][0]; } int main() { int n = 5; // 节点数 // 示例树: 1-2, 1-3, 2-4, 2-5 tree[1].push_back(2); tree[2].push_back(1); tree[1].push_back(3); tree[3].push_back(1); tree[2].push_back(4); tree[4].push_back(2); tree[2].push_back(5); tree[5].push_back(2); preprocess(1, n); // 根节点为1 cout << "LCA(4,5): " << lca(4,5) << endl; // 输出2 cout << "LCA(3,4): " << lca(3,4) << endl; // 输出1 return 0; } ``` - **代码说明**: - `preprocess` 初始化深度和祖先表,需在查询前调用一次。 - `lca` 函数处理查询,时间复杂度 $O(\log n)$。 - 示例树中,节点4和5的LCA是2,节点3和4的LCA是1[^4]。 #### 4. **关键优化与注意事项** - **空间优化**:祖先表使用 $O(n \log n)$ 空间,可通过稀疏表减少开销。 - **边界处理**:根节点的祖先标记为-1,避免越界。 - **性能**:预处理 $O(n \log n)$ 是主要开销,适合查询密集场景。 - **适用场景**:静态树(结构不变),如网络路由、基因树分析[^2]。 倍增法通过二进制分解实现高效跳步,是LCA问题的经典解法。实际应用中,可结合Tarjan或RMQ进一步优化[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值