以下是代码的逐行解释:
1. 头文件和常量定义
#include<bits/stdc++.h>
using namespace std;
const int N=500005;
#include<bits/stdc++.h>
:包含所有标准库头文件。using namespace std;
:使用标准命名空间。const int N=500005;
:定义常量N
,表示树的最大节点数。
2. 邻接表存储结构
struct Edge{
int to;
int next;
}edge[2*N+10]; // 稍大一些避免越界
int head[2*N+10],cnt;
Edge
结构体:表示一条边,包含to
(边的终点)和next
(下一条边的索引)。edge[2*N+10]
:存储所有边的数组,大小为2*N+10
(稍大一些避免越界)。head[2*N+10]
:head[u]
存储节点u
的第一条边的索引。cnt
:当前边的数量。
3. 初始化函数
void init(){
for(int i=0;i<2*N+10;i++){ // 从 0 开始
edge[i].next=-1;
head[i]=-1;
}
cnt=0;
}
- 初始化
edge
和head
数组,将所有值设为-1
,表示没有边。 cnt=0
:初始化边的数量为 0。
4. 添加边函数
void addedge(int u,int v){
edge[cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt++; // 头插法
}
- 将边
(u, v)
添加到邻接表中。 edge[cnt].to=v
:设置边的终点为v
。edge[cnt].next=head[u]
:将新边的next
指向head[u]
(当前的第一条边)。head[u]=cnt++
:更新head[u]
为新边的索引,并递增cnt
。
5. 倍增法预处理
int parent[N][20],depth[N];
parent[u][i]
:节点u
的第 (2^i) 个祖先。depth[u]
:节点u
的深度。
6. DFS 预处理函数
void dfs(int x,int father){
depth[x]=depth[father]+1;
parent[x][0]=father;
for(int i=1;(1<<i)<=depth[x];i++){
parent[x][i]=parent[parent[x][i-1]][i-1];
}
for(int i=head[x];i!=-1;i=edge[i].next){
if(edge[i].to!=father){
dfs(edge[i].to,x);
}
}
}
depth[x]=depth[father]+1
:计算节点x
的深度。parent[x][0]=father
:设置节点x
的第 (2^0) 个祖先为father
。for(int i=1;(1<<i)<=depth[x];i++)
:通过倍增法计算节点x
的第 (2^i) 个祖先。parent[x][i]=parent[parent[x][i-1]][i-1]
:利用递推公式计算。
for(int i=head[x];i!=-1;i=edge[i].next)
:遍历节点x
的所有邻接节点。if(edge[i].to!=father)
:如果邻接节点不是父节点,递归调用dfs
。
7. LCA 查询函数
int LCA(int x,int y){
if(depth[x]<depth[y]) swap(x,y);
// 将 x 跳到与 y 同一深度
for(int i=19;i>=0;i--){
if(depth[x]-(1<<i)>=depth[y]){
x=parent[x][i];
}
}
if(x==y) return x;
// x 和 y 同时向上跳
for(int i=19;i>=0;i--){
if(parent[x][i]!=parent[y][i]){
x=parent[x][i],y=parent[y][i];
}
}
return parent[x][0];
}
if(depth[x]<depth[y]) swap(x,y)
:确保x
是深度较大的节点。for(int i=19;i>=0;i--)
:将x
向上跳,直到与y
处于同一深度。if(depth[x]-(1<<i)>=depth[y])
:如果跳 (2^i) 步后深度仍大于等于y
,则跳。
if(x==y) return x
:如果x
和y
已经是同一个节点,直接返回。for(int i=19;i>=0;i--)
:x
和y
同时向上跳,直到它们的祖先相同。if(parent[x][i]!=parent[y][i])
:如果祖先不同,则跳。
return parent[x][0]
:返回x
和y
的最近公共祖先。
8. 主函数
int main(){
init();
int n,m,root;
cin>>n>>m>>root;
for(int i=1;i<n;i++){
int u,v;cin>>u>>v; // 修复输入
addedge(u,v);
addedge(v,u);
}
dfs(root,0);
while(m--){
int a,b;
cin>>a>>b;
cout<<LCA(a,b)<<endl;
}
return 0;
}
init()
:初始化邻接表。cin>>n>>m>>root
:输入节点数n
、查询数m
和根节点root
。for(int i=1;i<n;i++)
:输入树的边,并添加到邻接表中。dfs(root,0)
:从根节点开始 DFS,预处理深度和祖先信息。while(m--)
:处理每个查询,输出 LCA 结果。
总结
这段代码通过 邻接表存储树结构、DFS 预处理深度和祖先信息、倍增法优化 LCA 查询,实现了高效的 LCA 查询。其核心思想是:
- 预处理:通过 DFS 计算深度和祖先信息。
- 倍增法:通过 (2^i) 步跳跃,将查询复杂度从线性降低到对数级别。
- 邻接表:高效存储树结构。
通过理解这段代码,可以掌握树结构的基本操作和倍增法的应用,并推广到其他类似问题(如 RMQ、路径查询等)。