题目链接:https://www.luogu.org/problemnew/show/P3379

刚学了LCA,写篇blog加强理解。

LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。
———来自百度百科

例如:

在这棵树中 17 和 8 的LCA就是 3 。9 和 7 的LCA就是 7 。

明白了LCA后,就下来我们就要探讨探讨LCA怎么求了 qwq

  • 暴力算法
    以 17 和 18 为例,既然要求LCA,那么我们就让他们一个一个向上爬(我要一步一步往上爬 —— 《蜗牛》),直到相遇为止。,第一次相遇即是他们的LCA。
    模拟一下就是:
    17->14->10->7->3
    18->16->12->8->5->3
    最终结果就是 3。
    当然这个算法妥妥的会T飞掉,那么我们就要进行优化,于是就有了用倍增来加速的倍增LCA,这也是我们今天介绍的重点。
  • 倍增算法
    所谓倍增,就是按2的倍数来增大,也就是跳 1、2、4 、8 、16、32 …… 不过在这我们不是按从小到大跳,而是从大向小跳,即按……、32、16、8、4、2、 1、如果大的跳不过去,再把它调小。这是因为从小开始跳,可能会出现“悔棋”的现象。拿 5 为例,从小向大跳,5≠1+2+4,所以我们还要回溯一步,然后才能得出5=1+4;而从大向小跳,直接可以得出5=4+1。这也可以拿二进制为例,5(101),从高位向低位填很简单,如果填了这位之后比原数大了,那我就不填,这个过程是很好操作的。
    还是以 17 和 18 为例:
    17->3
    18->8->3
    可以看出向上跳的次数大大减小了。这个算法的时间复杂度为O(NlogN),已经很不错,可以满足大部分的需求了。
    想要实现这个算法,首先我们要记录各个点的深度和他们2i级的的祖先,用数组deepth表示每个节点的深度,fa[i][j]表示节点i的2j级祖先。
    代码如下:
void dfs(int f,int fath) //f表示当前节点,fath表示它的父亲节点
{
    deepth[f]=deepth[fath]+1;
    fa[f][0]=fath;
    for(int i=1;(1<<i)<=deepth[f];i++)
      fa[f][i]=fa[fa[f][i-1]][i-1]; //这个转移可以说是算法的核心之一
                                    //自己好好揣摩吧233
    for(int i=head[f];i;i=e[i].nex)
      if(e[i].t!=fath)
        dfs(e[i].t,f);
}

预处理完毕后,我们就可以去找它的LCA了,为了让它跑得快一些,我们可以加一个常数优化(来自洛谷提高组讲义)

for(int i=1;i<=n;i++) //预先算出log_2(n)的值,用的时候直接调用就可以了
      lg[i]=lg[i-1]+(1<<lg[i-1]==i);

接下来就是倍增LCA了,我们先把两个点提到同一高度,再统一开始跳。但我们在跳的时候不能直接跳到它们的LCA,因为这可能会误判,比如 4 和 8,在跳的时候,我们可能会认为 1 是它们的LCA,但 1 只是它们的祖先,它们的LCA其实是 3 。所以我们要跳到它们LCA的下面一层,比如 4 和 8 ,我们就跳到 4 和 5,然后输出它们的父节点,这样就不会误判了。

int lca(int x,int y)
{
    if(deepth[x]<deepth[y]) //用数学语言来说就是:不妨设x的深度 < y的深度
      swap(x,y);
    while(deepth[x]>deepth[y])
      x=fa[x][lg[deepth[x]-deepth[y]]-1]; //先跳到同一深度
    if(x==y)  //如果x是y的祖先,那他们的LCA肯定就是x了
      return x;
    for(int k=lg[deepth[x]];k>=0;k--) //不断向上跳(lg就是之前说的常数优化)
      if(fa[x][k]!=fa[y][k])  //因为我们要跳到它们LCA的下面一层,所以他们肯定要不相等,如果不相等我们就跳过去。
        x=fa[x][k], y=fa[y][k];
    return fa[x][0];  //返回父节点
}

总体来说差不多就是这样了,也不知道我这个蒟蒻讲的你们能不能看明白 orz。

完整代码:

#include<iostream>
#include<cstdio>
using namespace std;
struct yyy{
    int t,
        nex;
}e[2*500001];
int deepth[500001],fa[500001][22],lg[500001],head[500001];
int tot;
void add(int x,int y) //存树,类似于存图时的邻接表
{
    e[++tot].t=y;
    e[tot].nex=head[x];
    head[x]=tot;
}
void dfs(int f,int fath)
{
    deepth[f]=deepth[fath]+1;
    fa[f][0]=fath;
    for(int i=1;(1<<i)<=deepth[f];i++)
      fa[f][i]=fa[fa[f][i-1]][i-1];
    for(int i=head[f];i;i=e[i].nex)
      if(e[i].t!=fath)
        dfs(e[i].t,f);
}
int lca(int x,int y)
{
    if(deepth[x]<deepth[y])
      swap(x,y);
    while(deepth[x]>deepth[y])
      x=fa[x][lg[deepth[x]-deepth[y]]-1];
    if(x==y)
      return x;
    for(int k=lg[deepth[x]];k>=0;k--)
      if(fa[x][k]!=fa[y][k])
        x=fa[x][k], y=fa[y][k];
    return fa[x][0];
}
int n,m,s;
int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=n-1;i++)
    {
        int x,y;  scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
    }
    dfs(s,0);

    for(int i=1;i<=n;i++)
      lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    for(int i=1;i<=m;i++)
    {
        int x,y;  scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }

    return 0;
}

浅谈倍增LCA的更多相关文章

  1. 【数据结构】浅谈倍增求LCA

    思路 运用树上倍增法可以高效率地求出两点x,y的公共祖先LCA 我们设f[x][k]表示x的2k辈祖先 f[x][0]为x的父节点 因为从x向根节点走2k 可以看成从x走2k-1步 再走2k-1步 所 ...

  2. 浅谈倍增法求解LCA

    Luogu P3379 最近公共祖先 原题展现 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入格式 第一行包含三个正整数 \(N,M,S\),分别表示树的结点个数.询问 ...

  3. 浅谈求lca

    lca即最近公共祖先,求最近公共祖先的方法大概有3种,其实是窝只听说过3种,这3种做法分别是倍增求lca,树剖求lca和tarjan求lca,但是窝只会前2种,所以这里只说前2种算法了. 首先是倍增求 ...

  4. 浅谈 倍增/ST表

    命题描述 给定一个长度为 \(n\) 的序列,\(m\) 次询问区间最大值 分析 上面的问题肯定可以暴力对吧. 但暴力肯定不是最优对吧,所以我们直接就不考虑了... 于是引入:倍增 首先,倍增是个什么 ...

  5. Codeforces 418d Big Problems for Organizers [树形dp][倍增lca]

    题意: 给你一棵有n个节点的树,树的边权都是1. 有m次询问,每次询问输出树上所有节点离其较近结点距离的最大值. 思路: 1.首先是按照常规树形dp的思路维护一个子树节点中距离该点的最大值son_di ...

  6. 浅谈-RMQ

    浅谈RMQ Today,我get到了一个新算法,开心....RMQ. 今天主要说一下RMQ里的ST算法(Sparse Table). RMQ(Range Minimum/Maximum Query), ...

  7. 莫队浅谈&题目讲解

    莫队浅谈&题目讲解 一.莫队的思想以及莫队的前置知识 莫队是一种离线的算法,他的实现借用了分块的思想.在学习莫队之前,本人建议学习一下分块,并对其有一定的理解. 二.莫队 现给出一道例题:bz ...

  8. 浅谈C++ STL vector 容器

    浅谈C++ STL vector 容器 本篇随笔简单介绍一下\(C++STL\)中\(vector\)容器的使用方法和常见的使用技巧.\(vector\)容器是\(C++STL\)的一种比较基本的容器 ...

  9. 浅谈 Fragment 生命周期

    版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...

随机推荐

  1. Codeforces 625B【KMP】

     题意就是一个串在另一个串出现几次,但是字符不能重复匹配, 比如aaaaaaa aaaa的答案是1 思路: 本来写了个暴力过的,然后觉得KMP改改就好了,就让队友打了一个: #include < ...

  2. 新版蜀山剑侠传3D立体格斗全方位剖析

    国人终极格斗<蜀山剑侠传>,突破传统横版格斗"二次元"瓶颈,率先开辟3D立体横版格斗网游先河,实现"有纬度"的空间化立体式全景3D战斗体验!游戏大量 ...

  3. 蓝桥杯T42(八数码问题)

    题目链接:http://lx.lanqiao.cn/problem.page?gpid=T42 题意:中文题诶- 思路:bfs 将没种九宫格的状态看作一个状态节点,那么只需求起始节点到目标节点的最短路 ...

  4. Java之多线程优先级基础

    线程得到cpu的给的时间才能运行 有一个同步方法,里面有一个线程进去了,外面A,B俩线程在排队,A优先级比B优先级高,等到同步方法里面的线程出去了, 一定是A先进去; 但是: 因此,仅将高优先级赋予一 ...

  5. Bug调试(lldb)

    原文网址:http://www.cnblogs.com/Twisted-Fate/p/4760156.html 今天博主有一些Bug调试的需求,遇到了一些困难点,在此和大家分享,希望能够共同进步. X ...

  6. iOS7 UITableView Row Height Estimation

    This post is part of a daily series of posts introducing the most exciting new parts of iOS7 for dev ...

  7. Centos 5.11 升级 Openssl

    由于Openssl版本较低,故此升级版本来解决一些小问题. 1:查看openssl版本: [root@server-008 ~]# openssl versionOpenSSL 0.9.8e-fips ...

  8. 利用串口的硬件buf收发数据

    很多单片机的串口可以设置硬件接收和发送的buf,这样可以减少中断的次数和cpu的浪费,方法就是:发送时根据串口波特率(通讯格式N-8-1)和硬件buf缓冲的字节数计算定时器的间隔(小于1000*buf ...

  9. vue项目打包后文本溢出代码消失问题

    补充 https://www.cnblogs.com/richard1015/p/8526988.html vue webpack 打包编译-webkit-box-orient: vertical 后 ...

  10. [已读]编写高质量代码--Web前端开发修炼之道

    我觉得还蛮实用的一本,推荐看看,主要涉及到这些: 标签语义化.css模块化. css的一些东西,比如haslayout 文档流,还有如何实现水平.垂直居中. js代码组织与js分层.js压缩 编码规范 ...