洛谷p3379 最近公共祖先
·
以下是代码的逐行解释:
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、路径查询等)。
更多推荐



所有评论(0)