【模板】最近公共祖先(LCA)

相比于暴力算法倍增思想的LCA明显给省时间和空间

原理和同样是使用倍增思想的RMQ-ST 算法类似,比较简单,想清楚后很容易实现。

       对于每个节点u , fa[u][k] 表示 u 的第2k个祖先是谁。很容易就想到递推式: fa[j][i] = fa[fa[j][i - 1]][i - 1];  根据二进 制原理,理论上 u 的所有祖先都可以根据fa数组多次跳转得到,这样就间接地记录了每个节点的祖先信息。
     查询LCA(u,v)的时候:

         (一)u和v所在的树的层数如果一样,令u'=u。否则需要平衡操作(假设u更深),先找到u的一个祖先u', 使得u'的层数和v一样,此时LCA(u,v)=LCA(u',v) 。证明很简单:如果LCA(u,v)=v , 那么u'一定等于v ;如果LCA(u,v)=k ,k!=v ,那么k 的深度一定小于 v , u、u'、v 一定在k的子树中;综上所述,LCA(u,v)=LCA(u',v)一定成立。

         (二)此时u' 和 v 的祖先序列中一开始的部分一定有所重叠,重叠部分的最后一个元素(也就是深度最深,与u'、v最近的元素)就是所求的LCA(u,v)。这里fa数组就可以派上用场了。找到第一个不重叠的节点k,LCA(u,v)=fa[k][0] 。 找k的过程利用二进制贪心思想,先尽可能跳到最上层的祖先,如果两祖先相等,说明完全可以跳小点,跳的距离除2,这样一步步跳下去一定可以找到k。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int head[500010];
int fa[500010][20]={0};
int deep[500010]={0},cnt=0; //deep保存每个节点的深度,CNT为保存图的虚拟指针
int n,m,s;  // s为根节点编号
struct kk{
int next,v;
}e[1000010];  //保存边 无向图开成两倍哦
void add(int a,int b)
{
    cnt++;
    e[cnt].v=b;
    e[cnt].next=head[a];
    head[a]=cnt;
}  //加入边 so easy
void dfs(int x)
{
    for(int i=head[x];i;i=e[i].next)
    {
        int v=e[i].v;
        if(!deep[v])
        {
            deep[v]=deep[x]+1;fa[v][0]=x;
            dfs(v);
            }
        }
}   //dfs一次就可以保存下所有点的深度
int lca(int x,int y)
{
    if(deep[x]<deep[y])swap(x,y);  // 如果深度不同 调到同一深度
    for(int i=19;i>=0;i--)  //19为可能的最大深度
    {
        if(deep[fa[x][i]]>=deep[y])
        x=fa[x][i];
        }
    if(x==y)return x;
    for(int i=19;i>=0;i--)
    if(fa[x][i]!=fa[y][i])  //同时向上跳
    x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
int main()
{
   scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<n;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);add(b,a);
    }
    deep[s]=1;
    dfs(s);
    for(int i=1;i<=19;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++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
        }
 return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值