ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

LCA最近公共祖先

2021-02-27 21:35:53  阅读:168  来源: 互联网

标签:祖先 LCA here int depth 公共 节点


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

于是就出现了这种基于倍增的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;
}

代码比较丑,多多包含

标签:祖先,LCA,here,int,depth,公共,节点
来源: https://www.cnblogs.com/redintoncforever/p/14457240.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有