LCA

最近公共祖先 LCA 洛谷 P3379

今天 xiao 习了一下 LCA 过程十分曲折。。。

(检查的时候主函数没有调用 dfs,调试了好几遍才发现。。。
首先,我们先来看一看定义:最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。
它有三种做法:

  1. 暴力
  2. 倍增
  3. 树链剖分

遗憾的是我就会前两种;

第一种枚举

我们有了这么一棵树,和其中的两个节点;那么我们只需要一个一个求出它们的祖先,一步步向上跳就可以找到交点,找到第一个交点就是最近公共祖先
显而易见,这是最为朴素的正解;
但不是最优解;
仔细一想就知道他可顶复杂度超高(给两个叶子结点最后回到根上)
明显过不去
那么就要想方设法优化

第二种方法 倍增

倍增求 LCA 是最经典的方法了(不要问我谁提出来的问就是不知道)
上面朴素做法是一步一步向上跳,那么我们就可以很多步很多步向上跳,就可以优化;
而倍增就是优化的方法之一,以 logn 的时间复杂度来向上查询
只需要预处理出一个数组 f[u][i]u 是节点,i 是指 u 向上 2^i 个节点处(第 2^i 个祖先
那么我们就可以一步一步向上跳

第三种方法 树链剖分

我不会。。。……以后再写

上代码

#include<bits/stdc++.h>
using namespace std;
#define maxn 1000010
struct mm
{
    /* data */
    int next,v;
}cun[maxn];
int head[maxn],d[maxn],f[maxn][22],tot;
void add(int x,int y)
{
    cun[++tot].next=head[x];head[x]=tot;cun[tot].v=y;//结构体存链表
}
void dfs(int u,int fa)
{
    d[u]=d[fa]+1;//子节点深度是父节点加一
    for(int i=0;i<20;i++)
    {
        f[u][i+1]=f[f[u][i]][i];//可以自己推导(就是乘个二
    }
    for(int i=head[u];i;i=cun[i].next)
    {
        int a=cun[i].v;//邻接表向后查询
        if(a==fa)continue;//不查询父节点
        f[a][0]=u;//a是u的子节点,向上跳2^0是父节点u
        dfs(a,u);//递归
    }
}
int LCA(int x,int y)
{
    if(d[x]<d[y])swap(x,y);//后面方便
    for(int i=20;i>=0;i--)//从后向前找,二进制拆分
    {
        if(d[f[x][i]]>=d[y])x=f[x][i];//如果深度大就向上跳到相等为止
        if(x==y)return x;//如果同一深度时相等就相当于y是x的父节点
    }
    for(int i=20;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])//向上跳不相等的话就向上跳
        {
            x=f[x][i];y=f[y][i];//继续
        }
        /*由于向上跳容易误判,所以我们选择向上跳到同一父节点时停止,最后输出父节点
        向上跳无论如何都相等时就是同一父节点了*/
    }
    return f[x][0];//父节点便是LCA
}
int n,m,s;
int main()
{
    cin>>n>>m>>s;//输入
    for(int i=1;i<n;i++)
    {
        int x1,y1;
        cin>>x1>>y1;
        add(x1,y1);
        add(y1,x1);//无向图需要双向添加边
    }
    dfs(s,0);
    for(int i=1;i<=m;i++)
    {
        int x1,y1;
        cin>>x1>>y1;
        cout<<LCA(x1,y1)<<endl;//直接输出返回的就可以
    }
    return 0;
}

尾声 Johnsonloy 的 LCA 过啦(以后会补全树链剖分的内容滴 Q-Q

上一篇
下一篇