DFN序求LCA

这是一种 \(O(n \log n)\) 预处理,\(O(1)\) 查询的神奇求 LCA 科技
实际效率上和树链剖分差不多,但主要胜在代码短一点,查询快一些

那具体是怎么个事呢?

考虑对于下面这张图

显然,lca 节点的 DFN 序 是(这个子树)最小的

然后往下遍历到 \(u\) ,回到 lca 然后向右遍历到 \(v\) ,中途必然遍历到 lca 的一个叶子节点 \(d\)\(v\)\(d\) 的子树上

如图

那么显然, \(d\) 的 dfn 序就在 \(u,v\) 之间,且 \(d\) 是这段区间中深度最浅的那一个

所以,我们之只用求出 \([dfn[u]+1,dfn[v]]\) 之间的深度最浅的节点即可,而 LCA 就是这两个节点的父亲

然后就可以使使用 ST 表实现 \(O(n \log n)\) 预处理, \(O(1)\) 查询了

代码如下

#include <bits/stdc++.h>
using namespace std;
int n, dfn, m, s;
struct node
{
    int fa;
    int dfn;
    vector<int> son;
};
int st[20][1001000];
node tr[1000100];
int dfnmin(int x, int y)
{
    return tr[x].dfn < tr[y].dfn ? x : y;
}
void dfs(int x)
{
    tr[x].dfn = ++dfn;
    st[0][tr[x].dfn] = tr[x].fa;
    for (int son : tr[x].son)
    {
        if (son != tr[x].fa)
        {
            tr[son].fa = x;
            dfs(son);
        }
    }
}
void init()
{
    for (int i = 1; i <= __lg(n); i++)
    {
        for (int ww = 1; ww <= n; ww++)
        {
            st[i][ww] = dfnmin(st[i - 1][ww], st[i - 1][ww + (1 << (i - 1))]);
        }
    }
}
int get_lca(int a, int b)
{
    if (a == b)
    {
        return a;
    }
    int u = tr[a].dfn, v = tr[b].dfn;
    if (u > v)
    {
        swap(u, v), swap(a, b);
    }
    int d = __lg(v - u);
    u++;
    return dfnmin(st[d][u], st[d][v - (1 << d) + 1]);
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m >> s;
    int a, b;
    for (int ww = 1; ww < n; ww++)
    {
        cin >> a >> b;
        tr[a].son.push_back(b);
        tr[b].son.push_back(a);
    }
    dfs(s);
    init();
    for(int ww=1;ww<=m;ww++)
    {
        cin>>a>>b;
        cout<<get_lca(a,b)<<"\n";
    }
    return 0;
}

posted @   sea-and-sky  阅读(34)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示