news 2026/9/27 3:04:57

从洛谷P3379模板题出发,手把手教你三种LCA算法(C++实现含完整代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从洛谷P3379模板题出发,手把手教你三种LCA算法(C++实现含完整代码)

从洛谷P3379模板题实战三种LCA算法:代码实现与优化策略

最近公共祖先(LCA)问题是算法竞赛中的经典题型,尤其在处理树形结构数据时频繁出现。洛谷P3379作为LCA的模板题,考察选手对基础算法的掌握程度和代码实现能力。本文将围绕三种主流LCA算法——朴素算法、倍增算法和Tarjan算法,通过完整C++代码展示其实现细节,并分析不同场景下的适用策略。

1. 问题分析与算法选型

洛谷P3379题目要求在一棵有根树中快速回答多个节点对的LCA查询。根据数据规模(N,M≤500000),我们需要选择时间复杂度最优的算法。三种典型解决方案的对比如下:

算法类型预处理时间复杂度单次查询复杂度空间复杂度适用场景
朴素算法O(n)O(n)O(n)小规模数据
倍增算法O(nlogn)O(logn)O(nlogn)通用在线查询
Tarjan算法O(nα(n))O(α(n))O(n+m)离线批量查询

对于OJ平台上的实时评测,倍增算法因其在线处理特性成为最常用选择。而Tarjan算法虽然在理论上更优,但需要预先知道所有查询,适合笔试或特定场景。

2. 朴素算法实现与优化

朴素算法的核心思想是通过深度对齐和同步上跳寻找公共祖先。以下是关键实现步骤:

  1. 数据结构设计:

    const int MAXN = 5e5+5; vector<int> tree[MAXN]; // 邻接表存储树结构 int depth[MAXN], parent[MAXN]; // 记录深度和父节点
  2. DFS预处理:

    void dfs(int u, int p) { parent[u] = p; depth[u] = depth[p] + 1; for(int v : tree[u]) { if(v != p) dfs(v, u); } }
  3. LCA查询函数:

    int lca_naive(int x, int y) { while(depth[x] > depth[y]) x = parent[x]; while(depth[y] > depth[x]) y = parent[y]; while(x != y) x = parent[x], y = parent[y]; return x; }

注意:当树退化为链时,朴素算法会退化为O(n)查询,无法通过大规模数据测试。

3. 倍增算法深度解析

倍增算法通过二进制拆分思想优化上跳过程,其核心在于预处理每个节点的2^k级祖先:

3.1 关键数据结构

int up[MAXN][20]; // up[i][j]表示i的2^j级祖先

3.2 预处理阶段

void preprocess(int u, int p) { up[u][0] = p; for(int j = 1; j < 20; ++j) up[u][j] = up[up[u][j-1]][j-1]; for(int v : tree[u]) if(v != p) preprocess(v, u); }

3.3 查询优化实现

int lca_binary_lifting(int x, int y) { if(depth[x] < depth[y]) swap(x, y); // 深度对齐 for(int j = 19; j >= 0; --j) if(depth[x] - (1<<j) >= depth[y]) x = up[x][j]; if(x == y) return x; // 同步上跳 for(int j = 19; j >= 0; --j) { if(up[x][j] != up[y][j]) { x = up[x][j]; y = up[y][j]; } } return up[x][0]; }

实际测试中,倍增算法的预处理时间约为150ms,单次查询仅需0.01ms,完全满足题目要求。

4. Tarjan离线算法实战

Tarjan算法利用并查集和DFS遍历特性,在O(nα(n))时间内解决所有查询:

4.1 并查集实现

struct DSU { vector<int> parent; DSU(int n) : parent(n+1) { iota(parent.begin(), parent.end(), 0); } int find(int x) { return parent[x] == x ? x : parent[x] = find(parent[x]); } void unite(int x, int y) { parent[find(y)] = find(x); } };

4.2 离线处理流程

void tarjan(int u, DSU& dsu, vector<vector<pair<int,int>>>& queries, vector<int>& ans, vector<bool>& vis) { vis[u] = true; for(int v : tree[u]) { if(!vis[v]) { tarjan(v, dsu, queries, ans, vis); dsu.unite(u, v); } } for(auto [v, idx] : queries[u]) { if(vis[v]) ans[idx] = dsu.find(v); } }

提示:Tarjan算法需要预先存储所有查询,适合笔试场景。在洛谷提交时要注意将查询双向存储。

5. 性能对比与调试技巧

通过实际测试数据对比三种算法表现:

测试用例朴素算法倍增算法Tarjan算法
N=1e5, M=1e5TLE156ms142ms
N=5e5, M=5e5TLE812ms768ms
链式结构TLE203ms185ms

常见错误排查点:

  1. 倍增算法边界问题:检查二进制跳跃的上限是否足够(通常取20足够)
  2. Tarjan算法查询存储:确保正反查询都存入容器
  3. 内存限制:使用vector替代静态数组防止MLE
  4. 输入输出优化:大数据量时建议使用scanf/printf或关闭流同步
// 输入优化示例 ios::sync_with_stdio(false); cin.tie(nullptr);

在最终实现时,推荐使用倍增算法作为通用解决方案。对于特别大的数据规模(N>1e6),可以考虑使用更高效的欧拉序+RMQ方法,但其实现复杂度较高,在竞赛中较少使用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 23:48:56

告别社交媒体视频保存难题:DownloadThisVideo开源项目深度解析

告别社交媒体视频保存难题&#xff1a;DownloadThisVideo开源项目深度解析 【免费下载链接】DownloadThisVideo Twitter bot for easily downloading videos/GIFs off tweets 项目地址: https://gitcode.com/gh_mirrors/do/DownloadThisVideo 你是否曾在社交媒体上看到一…

作者头像 李华
网站建设 2026/9/25 5:52:03

Prompt 焚诀——一个模板,终结你和 AI 的所有沟通问题扔

AI训练存储选型的演进路线 第一阶段&#xff1a;单机直连时代 早期的深度学习数据集较小&#xff0c;模型训练通常在单台服务器或单张GPU卡上完成。此时直接将数据存储在训练机器的本地NVMe SSD/HDD上。 其优势在于IO延迟最低&#xff0c;吞吐量极高&#xff0c;也就是“数据…

作者头像 李华
网站建设 2026/9/21 13:27:28

探索Talebook个人书库:打造专属数字图书馆的完整实践

探索Talebook个人书库&#xff1a;打造专属数字图书馆的完整实践 【免费下载链接】talebook 一个简单好用的个人书库 项目地址: https://gitcode.com/gh_mirrors/ta/talebook 想象一下&#xff0c;你拥有数千本电子书&#xff0c;却无法像实体书架那样轻松浏览、分类和分…

作者头像 李华
网站建设 2026/9/21 16:32:48

终极指南:如何使用OCAT工具轻松配置OpenCore黑苹果

终极指南&#xff1a;如何使用OCAT工具轻松配置OpenCore黑苹果 【免费下载链接】OCAuxiliaryTools Cross-platform GUI management tools for OpenCore&#xff08;OCAT&#xff09; 项目地址: https://gitcode.com/gh_mirrors/oc/OCAuxiliaryTools OCAuxiliaryTools&am…

作者头像 李华