洛谷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;
}
  • 初始化 edgehead 数组,将所有值设为 -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:如果 xy 已经是同一个节点,直接返回。
  • for(int i=19;i>=0;i--)xy 同时向上跳,直到它们的祖先相同。
    • if(parent[x][i]!=parent[y][i]):如果祖先不同,则跳。
  • return parent[x][0]:返回 xy 的最近公共祖先。

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 查询。其核心思想是:

  1. 预处理:通过 DFS 计算深度和祖先信息。
  2. 倍增法:通过 (2^i) 步跳跃,将查询复杂度从线性降低到对数级别。
  3. 邻接表:高效存储树结构。

通过理解这段代码,可以掌握树结构的基本操作和倍增法的应用,并推广到其他类似问题(如 RMQ、路径查询等)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值