ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

最近公共祖先学习笔记

2022-08-07 14:00:09  阅读:156  来源: 互联网

标签:dep fa 祖先 LCA ++ 笔记 int 公共 dp


概念

在一棵有根树上,指定点集的最近公共祖先(即 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有