LCA最近公共祖先

原来学习最小生成树的时候写了道题,发现得用最近公共祖先迫于无奈便学了LCA(不过确实很有用)

LCA主要基于一种倍增的思想,和st表很像,通过预处理数据来花费空间换取时间。

要想学习怎么求LCA首先要学习LCA是什么。

LCA最近公共祖先

在一颗数中,比如说图中的4和5,那么他们的最近公共祖先便是2。

如果是2和3他们的最近公共祖先便是1,而2和4的最近公共祖先是2本身,所以最近公共祖先也可以是自己。

那么要如何来求呢。最朴素的做法是用dfs进行搜索查找,但这样的时间复杂度太高了。

于是就出现了这种基于倍增的LCA。

这里盗用一张洛谷的图,上面那图太小了

LCA最近公共祖先

------------恢复内容开始------------

原来学习最小生成树的时候写了道题,发现得用最近公共祖先迫于无奈便学了LCA(不过确实很有用)

LCA主要基于一种倍增的思想,和st表很像,通过预处理数据来花费空间换取时间。

要想学习怎么求LCA首先要学习LCA是什么。

LCA最近公共祖先

在一颗数中,比如说图中的4和5,那么他们的最近公共祖先便是2。

如果是2和3他们的最近公共祖先便是1,而2和4的最近公共祖先是2本身,所以最近公共祖先也可以是自己。

那么要如何来求呢。最朴素的做法是用dfs进行搜索查找,但这样的时间复杂度太高了。

于是就出现了这种基于倍增的LCA。

这里盗用一张洛谷的图,上面那图太小了

LCA最近公共祖先

比如说我们想求17和8的公共祖先,我们便要考虑将17和8给弄到同一层(即树的深度,第一层的深度是1)

17比8的深度要深,所以得先把17往上面爬。

如果一层一层的爬,那效率便太低了,所以我们便考虑一此直接爬2^n层。

比如这里17爬到10,我们直接爬2^1层,便不用爬两次了。

爬到相同的层数之后,我们还要继续爬,不过这时是两边一起爬相同的层数,也是一次爬2^n层,一直到爬到相同的节点便ok了,这时的这个节点便是我们要找的最近公共祖先。

讲了那么多现在最重要的便是如何实现了。

话不多说先上代码再讲解(这里是数据的预处理阶段采用dfs)

void dfs(int here,int fa) {//here表示目前所在的节点,fa表示当前节点的父节点
    vis[here] = 1;//用一个数组储存是否到过这个节点其实没什么用
    depth[here] = depth[fa] + 1; f[here][0] = fa;//depth代表节点深度的数组
    for (int i = 1; i <= lg[depth[here]]; i++) //lg数组代表着对里面的数取log2,也可以用log2()函数代替,不过会慢?
        f[here][i] = f[f[here][i - 1]][i - 1];//这里是一个dp,也是倍增的核心,主要原理是2^i=2^(i-1)*2^(i-1)
    for (int i = head[here]; i; i = a[i].nex)//这里是链式前向星存边没啥好说的
        if(!vis[a[i].to])
            dfs(a[i].to, here);
}

这步预处理是算法的核心,而这步的核心便是对二维数组f[i][j]的操作

这个数组中储存的数代表着i这个节点往上跳2^j步所到的节点。

预处理完了便是查找了。

int LCA(int x,int y) {
    if (depth[x] < depth[y])swap(x, y);//由于我们要找个深度最深的先往上跳所以无论输入的xy哪个最深,我们让x在树中深度最深,然后对x进行操作
    while (depth[x] > depth[y]) x = f[x][lg[depth[x] - depth[y]]];//用这个循环来让x和y达到同一层
    if (x == y)        return x;//特判一下如果如果xy此时节点相同的化,直接return,节约时间。
    for (int i = lg[depth[x]]; i >= 0; i--) {
        if (f[x][i] != f[y][i]) {
            x = f[x][i];
            y = f[y][i];
        }
    }//让x和y一同往上跳,如过跳到下一层他们就相同就不跳了
    return f[x][0];因为下一层才相同,所以return x往上跳2^0层
}

这样LCA大概就讲完了,最后附上全部代码

#include<iostream>
#include<cmath>
#include<algorithm>
#include<stdio.h>
#include<vector>
#define maxx 1000005
using namespace std;
int head[maxx], now = 0, n, m, lg[maxx], f[maxx][32], depth[maxx], vis[maxx], p;
struct edge {
    int nex, to;
}a[maxx*2];
void add_edge(int at, int to) {
    a[++now].nex  = head[at];
    a[now].to = to;
    head[at] = now;
}
void swap(int& x, int& y) {
    int temp = x; x = y; y = temp;
}
void dfs(int here,int fa) {
    vis[here] = 1;
    depth[here] = depth[fa] + 1; f[here][0] = fa;
    for (int i = 1; i <= lg[depth[here]]; i++) {
        f[here][i] = f[f[here][i - 1]][i - 1];
    }
    for (int i = head[here]; i; i = a[i].nex)
        if(!vis[a[i].to])
            dfs(a[i].to, here);
}
int LCA(int x,int y) {
    if (depth[x] < depth[y])swap(x, y);
    while (depth[x] > depth[y]) x = f[x][lg[depth[x] - depth[y]]];
    if (x == y)        return x;
    for (int i = lg[depth[x]]; i >= 0; i--) {
        if (f[x][i] != f[y][i]) {
            x = f[x][i];
            y = f[y][i];
        }
    }
    return f[x][0];
}
int main() {
    cin >> n >> m >> p;
    for (int i = 1; i <= n-1; i++) {
        int at, to, l; cin >> at >> to;
        add_edge(at, to);
        add_edge(to, at);
    }
    lg[0] = -1;
    for (int i = 1; i <= n; i++) {
        lg[i] = lg[i >> 1] + 1;
    }
    dfs(p, 0);
    for (int i = 1; i <= m; i++) {
        int x, y; cin >> x >> y;
        cout << LCA(x, y) << endl;
    }
    return 0;
}

代码比较丑,多多包含

上一篇:介绍git clone --depth=1的用法


下一篇:[leetCode]513. 找树左下角的值