最近公共祖先(LCA)

\(\text{LCA}\)

定义 性质:

在有根树上,\(\text{LCA}\) 是指一个点集的公共祖先中深度最大的点。

  • \(\text{LCA}\) 具有可并性。

  • \(\text{LCA}(u,v)\)\(u,v\) 间的路径上。

  • \(h(i)\) 表示 \(i\) 与根的距离,则两点 \(u,v\) 的距离 \(=h(u)+h(v)-2h(\text{LCA}(u,v))\)

求法:

倍增。

\(fa_{u,i}\) 表示 \(u\) 的第 \(2^i\) 级祖先,在求 \(\text{LCA}(u,v)\) 时,先将 \(u,v\) 跳转到同一深度,再枚举 \(k\),尝试将 \(u,v\) 跳到自己的 \(k\) 级祖先,并保证不会多跳到它们的公共祖先上,即保证它们不同,如果它们不相等,则跳,否则不跳。

graph LR LCA((LCA))-->根 u-->t0(...)-->u跳到这里-->LCA Node1-->t1(v跳到这里)-->LCA v(v)-->t1 Node2-->t1

到最后时,\(u,v\) 恰好是 \(\text{LCA}\) 的孩子,\(fa_{u,0}\) 即为 \(u,v\)\(\text{LCA}\)

预处理 \(fa\) 的方法:\(DFS\)\(fa_{u,0}\) 为自己的父亲,因为 \(2^{i-1}+2^{i-1}=2^i\),所以 \(u\) 的第 \(2^{i-1}\) 级祖先的第 \(2^{i-1}\) 级祖先就是 \(u\) 的第 \(2^{i-1}+2^{i-1}=2^i\) 级祖先,所以转移方程为:

\[\large{fa_{u,i}=fa_{fa_{u,i-1},i-1}} \]

graph LR u--2^i-1-->v--2^i-1-->w u--2^i-1 + 2^i-1 = 2^i-->w

时间复杂度:设询问 \(m\) 次,\(\Theta(n\log n+m\log n)\)

代码:

#include<iostream>
#include<vector>
#define int long long
using namespace std;

const int N = 500010;
int lg[N];

int n, m, rt;
vector<int> G[N];
int de[N], fa[N][25];

void dfs(int fat, int u) // 预处理
{
    fa[u][0] = fat;
    de[u] = de[fat] + 1; // 初始化
    for(int i=1; i<=lg[de[u]]; i++)
        fa[u][i] = fa[fa[u][i-1]][i-1]; // 转移
    for(int i=0; i<G[u].size(); i++)
    {
        int v = G[u][i];
        if(v == fat)
            continue;
        dfs(u, v); // 搜索
    }
}

int LCA(int x, int y)
{
    if(de[x] < de[y]) // 保证y深于x,便于计算
        swap(x, y);
    while(de[x] > de[y])
        x = fa[x][lg[de[x] - de[y]] - 1]; // 统一深度
    if(x == y)
        return x; // 特判
    for(int k=lg[de[x]]-1; k>=0; k--)
        if(fa[x][k] != fa[y][k])
            x = fa[x][k],
            y = fa[y][k]; // 跳转到LCA的下方
    return fa[x][0];
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    for(int i=1; i<N; i++)
        lg[i] = lg[i-1] + ((1 << lg[i-1]) == i);
    cin >> n >> m >> rt;
    for(int i=1; i<n; i++)
    {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(0, rt);
    while(m --)
    {
        int x, y;
        cin >> x >> y;
        cout << LCA(x, y) << '\n';
    }
    return 0;
}
posted @ 2024-03-28 10:09  gctiruct  阅读(88)  评论(0)    收藏  举报