倍增法写lca(最近公共祖先)适合初学者

这里我们以这个为样例-->P3379 【模板】最近公共祖先(LCA) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)z在下只是一个弱鸡,欢迎大佬们提醒

如果有小伙伴不知道倍增的话可以看看这个博客:【算法学习笔记】倍增 - RioTian (cnblogs.com)https://www.cnblogs.com/RioTian/p/14542241.html

这里我会介绍两个代码主要讲解第一个代码,第一个更易于理解第二个则是优化后的简便代码,接下来步入正题。 

第一个是我找了很多的代码东拼西凑的代码

先来讲解第一个我的垃圾代码

第一个是图论的基本建边的模板

void add(int u,int v)// 图论建边
{
    e[++idx]=v;ne[idx]=h[u];h[u]=idx;
}

这里写这个是为了减少复杂度,由于我的方向不是图论,这个模板的解释有不少好的博客,这个我不做解释,可以搜一下,弱鸡的悲哀(呜呜呜~

void dfs(int now,int dep,int fat)// now是现在遍历到的点,fat是父节点
{
    if(vis[now]) return;// 若该点访问过直接return,这里是更新所有点的父节点与该点的深度
    vis[now]=1;// 改为访问过
    depth[now]=dep;// 深度
    fa[now][0]=fat;//更新他的父节点
    for(int i=h[now];i;i=ne[i])
    {
        dfs(e[i],dep+1,now);// 深度加一,该点变成子树的父节点
    }
} 

 这里fa[i][j]数组是写他以上的节点的,fa[i][0]对应该点的父节点,fa[i][1]就是该点的父节点的父节点.......以此类推。depth数组是每一节点在二叉树上的深度,例如下图中第一个图D点深度是3

 for (int i = 1; (1<<i) <= n; i++)
    {
        for(int j=1;j<=n;j++)
        {
            fa[j][i]=fa[fa[j][i-1]][i-1];
        }
    }

这里倍增用的就是dp写递推公式就是fa[i][j]=fa[fa[i][j-1]][j-1],大概意思就是爷爷是爸爸的爸爸,

 LL lca(int x,int y)
{
    if(depth[x]<depth[y]) swap(x,y);// 这里是让
    for(int i=23;i>=0;--i)
    {
        if(fa[x][i]!=0&&depth[fa[x][i]]>=depth[y])// 这里因为fa[x][0]是该节点的父节点,所以这个while就是不断地找父节点,同时深度减一,直到xy深度相同
        {
            x=fa[x][i];
        }
    }
    if(x==y) return x;
    for(int i=23;i>=0;--i)
    {
        if(fa[x][i]!=0&&fa[y][i]!=0&&fa[x][i]!=fa[y][i])// 因为深度相同,此时变成同一深度,同时向上找祖先
        {
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}

 这里就是lca的核心代码了,解释一下因为题目最多是5e5按完全二叉树看5e5最多只能分布到第20至第21层我写了23次循环是为了防爆(闲的哈哈哈哈~),这块主要分为两部分第一个是x和y找最近公共祖先,为了方便运算先将x和y找到同一深度的自己的祖先节点,这样循环后面就可以用一个循环直接让他们同时找祖先并且祖先是一个深度知道祖先相同推出,则该点就是最近公共祖先。

总的代码如下:

#include<iostream>
#include<cmath>
#include<cstring>
#include<set>
#include<algorithm> 
using namespace std;
typedef long long LL;
LL fa[1000500][30],depth[1000500];
LL e[1000500],h[1000500],ne[1000500];
bool vis[1000500];
LL n, m, t,idx=0;
void add(int u,int v)// 经典图论建边

{
    e[++idx]=v;ne[idx]=h[u];h[u]=idx;
}
void swap(int &x,int &y)
{
    LL k=x;
    x=y;
    y=k;
}
void dfs(int now,int dep,int fat)
{
    if(vis[now]) return;
    vis[now]=1;
    depth[now]=dep;
    fa[now][0]=fat;
    for(int i=h[now];i;i=ne[i])
    {
        dfs(e[i],dep+1,now);
    }
}
LL lca(int x,int y)
{
    if(depth[x]<depth[y]) swap(x,y);
    for(int i=23;i>=0;--i)
    {
        if(fa[x][i]!=0&&depth[fa[x][i]]>=depth[y])
        {
            x=fa[x][i];
        }
    }
    if(x==y) return x;
    for(int i=23;i>=0;--i)
    {
        if(fa[x][i]!=0&&fa[y][i]!=0&&fa[x][i]!=fa[y][i])
        {
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}
int main()
{
    scanf("%lld %lld %lld",&n,&m,&t);
    for(int i=1;i<=n-1;i++)
    {
        LL x,y;
        scanf("%lld %lld",&x,&y);
        add(x,y);// 双向建边
        add(y,x);
    }
    dfs(t,1,0);
    for (int i = 1; (1<<i) <= n; i++)
    {
        for(int j=1;j<=n;j++)
        {
            fa[j][i]=fa[fa[j][i-1]][i-1];
        }
    }
    for(int i=1;i<=m;i++)
    {
        LL x,y;
        scanf("%lld %lld",&x,&y);
        cout<<lca(x,y)<<endl;
    }
}

 以上是我的代码,我觉得理解层面上会简单一点,但是总的写的有点复杂。所以再给你们带来个好的模板。

第二个自行体会只要理解第一个第二个基本就没问题,废话不多说上代码:

#include<iostream>
#include<cmath>
#include<cstring>
#include<set>
#include<algorithm> 
using namespace std;
const int N = 1e6;
const int M = 21;
int e[N], ne[N], h[N], idx;
int fa[N][M], dep[N];
void add(int u, int v)
{
    e[++idx] = v, ne[idx] = h[u], h[u] = idx;
}
void dfs(int u ,int fath )
{
    fa[u][0] = fath, dep[u] = dep[fath] + 1;
    for (int i = 1; i < M; i++)
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    for (int i = h[u]; i; i = ne[i])
    {
        int v = e[i];
        if (v != fath)dfs(v, u);
    }
}
int lca(int u, int v)
{
    if (dep[u] < dep[v]) swap(u, v);
    for (int i = M - 1; i + 1; i--)
        if (dep[fa[u][i]] >= dep[v])u = fa[u][i];
            if (u == v)return u;
    for (int i = M - 1; i + 1; i--)
        if (fa[u][i] != fa[v][i])
            u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}
int main()
{
    ios::sync_with_stdio(0), cout.tie(0), cin.tie(0);
    int n, m, s, u, v;
    cin >> n >> m >> s;
    for (int i = 1; i < n; i++)
    {
        cin >> u >> v;
        add(u, v),add(v, u);
    }
    dfs(s,0);
    while (m--)
    {
        cin >> u >> v;
        cout << lca(u, v) << '\n';
    }
}

最后以上仅代表我的理解,如果有什么跟好的提示可以私信告诉我或者评论里说一下,我会可能完善,同时是我个人的记录,禁止抄袭同时没有冒犯的意思,谢谢观看 嘿嘿~

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cmq0616~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值