• 『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
    ", lca(x, y));
    	} return 0;
    }
    
  • 相关阅读:
    蚂蚁森林自动收能量
    え速记
    い 速记
    あ速记
    五十音图
    Leetcode 215. 数组中的第K个最大元素 排序 优先队列
    动手学深度学习 | 第二部分完结竞赛:图片分类 | 29
    动手学深度学习 | 残差网络 ResNet | 27
    动手学深度学习 | 批量归一化 | 26
    动手学深度学习 | 含并行连结的网络GoogLeNet/Inception V3 | 25
  • 原文地址:https://www.cnblogs.com/Nicoppa/p/11471149.html
Copyright © 2020-2023  润新知