最近公共祖先LCA(学习笔记)

· · 算法·理论

LCA

P3379 【模板】最近公共祖先(LCA)

定义:

在一棵树上,任意两个节点,往上找的最近的公共根节点就是他们两个的最近公共祖先LCA

作用:

可用于求树上任意两个点的路径和其路径的权值关系,如边权最大值,边权和等,都是以logn的复杂度

实现:

倍增:

倍增,顾名思义,运用了指数打表的思想。

1.我们要用dfs遍历每一点,预处理求出每一节点的i次倍后的父亲节点,及f_{x,i}表示点x2^i个祖先,并且找到每一个节点的深度

void dfs(int u,int fa){
    d[u]=d[fa]+1;
    f[u][0]=fa;
    for(int i=1;(1<<i)<=d[u];i++)f[u][i]=f[f[u][i-1]][i-1];
    for(int i=0;i<e[u].size();i++){
        int v=e[u][i];
        if(v==fa)continue;
        dfs(v,u);
    }
}

2.倍增求LCA时,我们先要确保要找的点在同一深度。让后一起向上跳求LCA。因此先要判断两节点的深度,在将深的往上跳,直到跳到同一深度。这时要判断是否相等,预防一个节点就是另一节点的根节点。相等则直接返回

3.等两节点在同一高度后,在同时向上跳,知道跳完,这是,两节点所在节点就是LCA,输出即可,若要求路径关系,在dfs时顺便也预处理一下,数值,后面相同

int lca(int x,int y){
    if(d[x]<d[y])swap(x,y);
    for(int i=20;i>=0;i--){
        if(d[f[x][i]]<d[y])continue;
        x=f[x][i];
    }
    if(x==y)return x;
    for(int i=20;i>=0;i--){
        if(f[x][i]==f[y][i])continue;
        x=f[x][i];
        y=f[y][i];
    }
    return f[x][0];
}

注意:

1.dfsf_{i,0}就是其父节点
2.要给根节点的父节点和根节点深度赋值

完整代码:

#include<bits/stdc++.h>
using namespace std;
const int N=6e5+10;
vector<int>e[N];
int f[N][32],d[N];
void dfs(int u,int fa){
    d[u]=d[fa]+1;
    f[u][0]=fa;
    for(int i=1;(1<<i)<=d[u];i++)f[u][i]=f[f[u][i-1]][i-1];
    for(int i=0;i<e[u].size();i++){
        int v=e[u][i];
        if(v==fa)continue;
        dfs(v,u);
    }
}
int lca(int x,int y){
    if(d[x]<d[y])swap(x,y);
    for(int i=20;i>=0;i--){
        if(d[f[x][i]]<d[y])continue;
        x=f[x][i];
    }
    if(x==y)return x;
    for(int i=20;i>=0;i--){
        if(f[x][i]==f[y][i])continue;
        x=f[x][i];
        y=f[y][i];
    }
    return f[x][0];
}
int main(){
    int n,m,s;
    cin>>n>>m>>s;
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    d[s]=1;
    dfs(s,0);
    while(m--){
        int u,v;
        cin>>u>>v;
        cout<<lca(u,v)<<endl;
    }
}

练习:

1.Cheap Robot 题解
2.P4281 [AHOI2008] 紧急集合 / 聚会 题解
3.P5836 [USACO19DEC] Milk Visits S 题解