其他分享
首页 > 其他分享> > 最近公共祖先学习笔记

最近公共祖先学习笔记

作者:互联网

概念

在一棵有根树上,指定点集的最近公共祖先(即 LCA ),就是这些节点的祖先集合的并集中离根最远的点

实现

暴力

先对树进行一次深搜,预处理出每个节点的父亲与深度

对于每一次查询,我们先让深度较大的点向上跳,直到两点深度相同为止

接下来让这两个点一起向上跳,直到这两点相遇为止,此时该节点就是这两个点的 LCA

倍增优化暴力

我们令 \(dp_{i,j}\) 表示节点 \(i\) 的第 \(2^j\) 级祖先(即 \(i\) 要向上跳 \(2^j\) 次到达 \(dp_{i,j}\) )

显然易得式子:

dp[i][j] = dp[dp[i][j - 1]][j - 1];

我们先放上深搜预处理的代码

inline void dfs(int u, int fa) {
    dp[u][0] = fa, dep[u] = dep[fa] + 1;

    for (int i = 0; i < LOG[dep[u]]; ++i)
        dp[u][i + 1] = dp[dp[u][i]][i];

    for (int i = 0, v; i < edge[u].size(); ++i) {
    	v=edge[u][i];
        if (v != fa)
            dfs(v, u);
    }
}

我们考虑暴力的第一步:让深度较大的点向上跳,直到两点深度相同为止

考虑对两点的深度差进行二进制拆分,利用倍增数组向上跳

接着考虑暴力第二步:让这两个点一起向上跳,直到这两点相遇为止

我们利用倍增的思想,从大到小枚举两点的 \(2^k\) 级祖先,判断其是否相等,找出最近公共祖先的儿子,最后返回其父亲即可

查询 LCA 代码:

inline int LCA(int x, int y) {
    if (dep[x] < dep[y])
    	swap(x, y);
        
    for (int i = 0, h = dep[x] - dep[y]; h; ++i, h >>= 1)
    	if (h & 1)
    		x = dp[x][i];

    if (x == y)
        return x;

    for (int i = LOG[dep[x]]; ~i; --i)
        if (dp[x][i] != dp[y][i])
            x = dp[x][i], y = dp[y][i];

    return dp[x][0];
}

用欧拉序列转化为 RMQ 问题

前置芝士:欧拉序列

对一棵树进行深搜,无论是访问还是回溯,每次到达一个结点都将编号记录下来,可以得到一个长度为 \(2n-1\) 的序列,这个序列被称作这棵树的欧拉序列 \(F\)

记点 \(u\) 在欧拉序列中第一次出现的位置为 \(pos_u\)

不难发现,在欧拉序列中 \(u \to v\) 的路径会经过 \(LCA(u,v)\) 且不会经过 \(LCA(u,v)\) 的各级祖先,所以得出一下结论:

\[LCA(u,v) = \min_{i = pos_u}^{pos_v} F_i \]

直接用 ST 表解决即可

inline void dfs(int u, int fa) {
    dep[u] = dep[fa] + 1;
    dfn[++ cnt] = u,pos[u] = cnt;

    for (int i = 0, v; i < edge[u].size(); ++i) {
        v = edge[u][i];

        if (v == fa)
            continue;

        dfs(v, u);
        dfn[++ cnt] = u;
    }

    return ;
}

inline void init() {
    LOG[0] = -1;

    for (int i = 1; i <= cnt; ++ i)
        LOG[i] = LOG[i >> 1] + 1;

    for (int i = 1; i <= cnt; ++ i)
        dp[i][0] = dfn[i];

    for (int j = 1; j <= LOG[cnt]; ++ j)
        for (int i = 1; i <= cnt - (1 << j) + 1; ++ i)
            if (dep[dp[i][j - 1]] < dep[dp[i + (1 << (j - 1))][j - 1]])
                dp[i][j] = dp[i][j - 1];
            else
                dp[i][j] = dp[i + (1 << (j - 1))][j - 1];
}

inline int query(int l, int r) {
    int k = LOG[r - l + 1];
    return dep[dp[l][k]] < dep[dp[r - (1 << k) + 1][k]] ? dp[l][k] : dp[r - (1 << k) + 1][k];
}

inline int LCA(int x, int y) {
    int l = pos[x], r = pos[y];

    if (l > r)
        swap(l, r);

    return query(l, r);
}

树链剖分

将两个点向上跳到同一条重链上,深度较浅的节点就是它们的 LCA

标签:dep,fa,祖先,LCA,++,笔记,int,公共,dp
来源: https://www.cnblogs.com/wshcl/p/LCA.html