1. 树上倍增算法与LCA问题解析
第一次接触树上倍增算法时,我被它优雅的思想所震撼。这个算法完美结合了动态规划和二分查找的精髓,将看似复杂的树上路径查询问题转化为高效的预处理+快速查询模式。在实际工程中,无论是社交网络的关系链分析,还是路由算法的路径优化,LCA(最近公共祖先)问题都扮演着重要角色。
理解这个算法需要掌握三个关键点:首先是树的父节点表示法,这是所有操作的基础;其次是动态规划预处理的思想,通过递推构建倍增数组;最后是查询时的二进制拆分技巧,这是算法高效的核心所在。下面我将通过模板题的实现,带大家深入理解这个经典算法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法原理与实现细节
2.1 基础数据结构设计
实现树上倍增算法,首先需要合理设计数据结构。我通常采用邻接表存储树结构,因为它的空间复杂度是O(n),且能高效遍历子节点。对于一棵n个节点的树,我们需要定义以下核心数据结构:
cpp复制const int MAXN = 1e5 + 5;
const int LOG = 20; // 足够覆盖1e5规模的树
vector<int> tree[MAXN]; // 邻接表存储树结构
int depth[MAXN]; // 每个节点的深度
int up[MAXN][LOG]; // 倍增数组
这里LOG的取值很关键,它决定了预处理的空间复杂度。对于n个节点的树,LOG取⌈log₂n⌉即可。例如n=1e5时,LOG=17足够(因为2^17=131072)。我习惯取20作为安全值,这样即使n达到1e6也能应对。
2.2 预处理阶段实现
预处理阶段采用DFS遍历树,同时填充倍增数组。这个阶段的时间复杂度是O(nlogn),是算法的主要耗时部分。具体实现时要注意几个细节:
cpp复制void dfs(int u, int parent) {
up[u][0] = parent; // 直接父节点
for(int i = 1; i < LOG; i++) {
up[u][i] = up[up[u][i-1]][i-1]; // 动态规划递推
}
for(int v : tree[u]) {
if(v != parent) {
depth[v] = depth[u] + 1;
dfs(v, u);
}
}
}
这里容易犯的错误是忘记检查v != parent的条件,导致无限递归。另一个常见问题是数组越界,特别是在处理根节点时,需要确保up[root][i]有合理的默认值(通常设为root自身或-1等特殊值)。
2.3 LCA查询实现
查询两个节点u和v的LCA时,算法分为三个步骤:首先将两个节点调整
