『LCA』求最近公共祖先 倍增模板

学习LCA前提须知

LCA是在一棵树上求两个点的最近公共祖先。两个点共同能到达的点,这样的点我们称它为公共祖先,那么两个点共同能到达的第一个点,这样的点我们称它为最近公共祖先

算法内容

前置技能

您需要去了解 邻接表存图 倍增算法基本原理 高中必修一log函数计算

竞赛需要用到的点

1、LCA常作为思维题的工具,不单独考

2、倍增LCA必加优化,或者可以选择常数更优秀的树剖求LCA

倍增求LCA略讲

考虑常规算法求LCA,我们需要知道当前查找的两个点是否遍历过了同一个点上,若一旦有被两点都遍历到的点,那么当前点就是我们的最近公共祖先。我们现在的问题就是,如何找到这样的点?考虑从路径入手,我们一开始的朴素算法能够想到的就是一个一个往上走,但实际上走的很多点都不会用到,我们考虑一个二分的逆运算,倍增

我们从倍增入手,倍增的基本原理就是,从某一个点(数)出发,进行二的次幂级别的运动,并且判断当前是否满足条件,若不满足再次进行跳跃,每一次跳跃都是上一次跳跃路径长度的 2 倍。那就这样跳?肯定是不行的,因为你不知道何时停止,如果不加限制条件,那么就可能判断整棵树的根节点为它们的最近公共祖先,这肯定是错误的,那如何加限制条件呢?

我们现在的目标很明确,就是用倍增向上跳找到最近公共祖先,那我们应该怎样加限制条件呢?我们可以很轻松得到,当它们在同一深度时,若它们的节点不同,那么肯定是还没到达任何一个公共祖先,那么当我们满足 当前两点同深度,不同点并且不能够再往上跳 的时候,是不是意味着再往上走一个点就是我们的最终答案了呢?答案是肯定的。

那我们就可以开始写代码了

部分代码展现

首先是设变量和邻接表

//#define fre yes

#include <cstdio>
#include <cstring> // memset

const int N = 100005;
int head[N << 1], to[N << 1], ver[N << 1];
int depth[N], f[N][22], lg[N];
// depth代表深度 设f[x][k]的话就是 x点向上走2^k步
// lg就是我们的优化

int tot;
void addedge(int x, int y) {
    ver[tot] = y;
    to[tot] = head[x];
    head[x] = tot++;
}

int main() {
    memset(head, -1, sizeof(head));
    ...
}

然后我们需要来一个先将我们的depth数组和f数组起始化的函数,并且加上我们的优化

void dfs(int x, int fa) { //x为当前节点 fa为x的上一个节点
    depth[x] = depth[fa] + 1;
    f[x][0] = fa;
    for (int i = 1; (1 << i) <= depth[x]; i++) {
        f[x][i] = f[f[x][i - 1]][i - 1];
        //这里是一个推导公式
        //x向上走2^i 相当于x向上走2^{i - 1} + 2^{i - 1}
    }
    
    for (int i = head[x]; ~i; i = to[i]) {
        int v = ver[i];
        if(v != fa) {
            dfs(v, x);
        }
    }
}

int main() {
    static int n; //n个点
    ...
    for (int i = 1; i <= n; i++) {
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
    }
    dfs(root, -1);
    ...
}

起始化了之后就可以求我们的LCA了

#include <iostream>
int lca(int x, int y) {
    if(depth[x] < depth[y]) {
        std::swap(x, y);
    }
    
    while(depth[x] > depth[y]) {
        x = f[x][lg[depth[x] - depth[y]] - 1];
        //这样就能让x能跑到y的深度
    }
    
    if(x == y) return x; //如果直接相等了 那么x肯定是的最近公共祖先
    
    for (int k = lg[depth[x]] - 1; i >= 0; i--) {
        //这里也是一句优化 即跑到顶最少需要多少次2^k
        if(fa[x][k] != fa[y][k]) {
            //如果不相等 那么满足条件 向上跳
            x = fa[x][k];
            y = fa[y][k];
        }
    } return fa[x][0];
}

完整代码

//#define fre yes

#include <cstdio>
#include <cstring>
#include <iostream>

const int N = 500005;
int head[N << 1], ver[N << 1], to[N << 1];
int lg[N], depth[N], f[N][22];

int tot;
void addedge(int x, int y) {
    ver[tot] = y;
    to[tot] = head[x];
    head[x] = tot++;
}

void dfs(int x, int fa) {
    depth[x] = depth[fa] + 1;
    f[x][0] = fa;
    for (int i = 1; (1 << i) <= depth[x]; i++) {
        f[x][i] = f[f[x][i - 1]][i - 1];
    }
    
    for (int i = head[x]; ~i; i = to[i]) {
        int v = ver[i];
        if(v != fa) {
            dfs(v, x);
        }
    }
}

int lca(int x, int y) {
    if(depth[x] < depth[y]) {
        std::swap(x, y);
    }
    
    while(depth[x] > depth[y]) {
        x = f[x][lg[depth[x] - depth[y]] - 1];
    }
    
    if(x == y) return x;
    
    for (int k = lg[depth[x]] - 1; k >= 0; k--) {
        if(f[x][k] != f[y][k]) {
            x = f[x][k]; y = f[y][k];
        } 
    } return f[x][0];
}

int main() {
    memset(head, -1, sizeof(head));
    static int n, m, s;
    scanf("%d %d %d", &n, &m, &s);
    for (int i = 1; i < n; i++) {
        int x, y;
        scanf("%d %d", &x, &y);
        addedge(x, y);
        addedge(y, x);
    }
    
    for (int i = 1; i <= n; i++) {
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
    } dfs(s, 0);
    
    for (int i = 1; i <= m; i++) {
        int x, y;
        scanf("%d %d", &x, &y);
        printf("%d\n", lca(x, y));
    } return 0;
}

猜你喜欢

转载自www.cnblogs.com/Nicoppa/p/11471149.html