当前位置:首页 > GESP > 洛谷P10113题(2023年GESP八级):用LCA算法高效解决大量的工作沟通

洛谷P10113题(2023年GESP八级):用LCA算法高效解决大量的工作沟通

3个月前 (09-10)

洛谷P10113题(2023年GESP八级):用LCA算法高效解决大量的工作沟通 LCA算法 树结构 GESP GESP八级 ST表 二分查找 递归 第1张

一、题目解读

洛谷P10113题(2023年GESP八级)要求解决树结构中多个节点的最近公共祖先(LCA)查询问题。给定一棵及若干组节点,需快速找出每组节点的LCA。传统方法如暴力遍历时间复杂度较高,而本题采用倍增法(RMQ算法)优化,通过预处理实现O(logN)的查询效率,适用于大规模数据场景。

二、解题思路

核心是倍增法(基于ST表思想的变体),通过构建“倍增表”预处理每个节点的祖先信息。关键在于利用二进制拆分思想:

1. 预处理阶段:计算每个节点深度,构建up[i][j]表表示节点i的2^j层祖先。

2. 查询阶段:通过二分提升深度差异,再同步向上移动找到LCA。

此思路将单次查询复杂度降至O(logN),大幅优化性能。

三、解题步骤

1. 预处理阶段

● 初始化根节点(0号节点)深度为0,父节点为自身。

递归计算子节点深度:depth[i] = depth[parent[i]] + 1。

● 填充倍增表:up[i][j] = up[up[i][j-1]][j-1],即节点i的2^j祖先通过其2^(j-1)祖先的2^(j-1)祖先间接获得。

2. LCA查询

● 确保较深节点u与v深度对齐:通过二进制拆分快速提升u至与v同深度。

● 同步向上:从最高位开始,若u、v的2^j祖先不同,则同时向父节点移动。最终两者父节点即为LCA。

四、代码与注释

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int MAXN = 1e5 + 5;  
const int LOG = 20; // 足够大的对数级别  

vector<int> parent(MAXN); // 存储每个节点的直接领导  
vector<int> depth(MAXN);  // 存储每个节点的深度  
vector<vector<int>> up(MAXN, vector<int>(LOG)); // 倍增表  

// 预处理函数,构建倍增表  
void preprocess(int n) {  
    depth[0] = 0;  
    up[0][0] = 0; // 根节点的2^0祖先还是自己  
    for(int i = 1; i < n; ++i) {  
        depth[i] = depth[parent[i]] + 1;  
        up[i][0] = parent[i]; // 2^0祖先就是直接领导  
    }  
    for(int j = 1; j < LOG; ++j) {  
        for(int i = 0; i < n; ++i) {  
            up[i][j] = up[up[i][j-1]][j-1];  
        }  
    }  
}  

// 查找两个节点的LCA  
int lca(int u, int v) {  
    if(depth[u] < depth[v]) swap(u, v); // 确保u更深  
    for(int j = LOG-1; j >= 0; --j) {  
        if(depth[u] - (1 << j) >= depth[v]) {  
            u = up[u][j]; // 提升u  
        }  
    }  
    if(u == v) return u; // u和v已重合  
    for(int j = LOG-1; j >= 0; --j) {  
        if(up[u][j]!= up[v][j]) {  
            u = up[u][j];  
            v = up[v][j];  
        }  
    }  
    return parent[u]; // 最终LCA为u的父节点  
}  

int main() {  
    ios::sync_with_stdio(false);  
    cin.tie(nullptr);  
    int N; cin >> N;  
    // 读取每个员工的直接领导
    for(int i = 1; i < N; ++i) cin >> parent[i];  
    // 预处理倍增表
    preprocess(N);  
    int Q; cin >> Q;  
    while(Q--) {  
        int m; cin >> m;  
        vector<int> employees(m);  
        for(int i = 0; i < m; ++i) cin >> employees[i];  
         // 找出所有员工的LCA
        int current_lca = employees[0];  
        for(int i = 1; i < m; ++i) {  
            current_lca = lca(current_lca, employees[i]);  
        }  
        cout << current_lca << '\n';  
    }  
}

注释说明:

● up[i][j] 存储节点i的2^j层祖先,预处理通过迭代构建。

● lca(u, v) 函数通过二分法对齐深度,再同步向上查找LCA。

五、总结

本文通过洛谷P10113题的代码解析,展示了倍增法在LCA问题中的高效应用。该算法通过预处理空间换取查询时间,尤其适用于需要频繁查询LCA的场景。代码结构清晰,预处理与查询分离,可扩展性强。理解此解法有助于优化树结构相关算法设计,提升解题效率。


原创内容 转载请注明出处

分享给朋友:

相关文章

力扣第654题:最大二叉树解题教程 用数组构造最大二叉树

力扣第654题:最大二叉树解题教程 用数组构造最大二叉树

题目解读给定一个不含重复元素的整数数组,我们需要构建一棵最大二叉树。构建规则是:数组中的最大值作为根节点,其左侧子数组构建左子树,右侧子数组构建右子树,然后递归地应用这个规则。这种构建方式体现了分治思...

牛客23458题解析:基于二分查找的动态规划解法与代码实现

牛客23458题解析:基于二分查找的动态规划解法与代码实现

一、题目解读牛客23458题要求将给定的整数数组划分为m个连续子数组,使得每个子数组的和不超过某个最大值,且该最大值尽可能小。题目本质是求解“最小化最大值”的优化问题,需要结合二分查找与动态规划思想,...

【力扣2846题】图论+二进制提升:高效解决连通性问题(附C++代码)

【力扣2846题】图论+二进制提升:高效解决连通性问题(附C++代码)

一、题目解读力扣2846题要求解决一个基于图连通性的操作优化问题。给定一个无向图,包含边权重,以及一系列查询,每个查询询问两点间路径的最小操作次数。题目关键在于高效计算路径上权重分布的统计信息,并转化...

洛谷P1438题解:基于线段树的等差数列

洛谷P1438题解:基于线段树的等差数列

一、题目解读洛谷P1438题要求处理数列的区间更新与单点查询操作,其中更新方式为给定区间内元素按等差数列递增。传统暴力修改会因多次区间遍历导致超时,需设计高效数据结构——线段树,结合等差数列性质实现O...

【牛客4456题解析】最长上升子序列的动态规划+二分查找解法

【牛客4456题解析】最长上升子序列的动态规划+二分查找解法

一、题目解读牛客4456题要求在一个整数序列中寻找最长上升子序列(LIS)的长度。题目考察的核心是动态规划与高效查找算法的结合,需要设计一种能在给定序列中快速找到最长递增子序列的方法,通常需要平衡时间...

2023年GESP五级烹饪问题解题指南:位运算优化AND最大值求解

2023年GESP五级烹饪问题解题指南:位运算优化AND最大值求解

一、题目解读2023年GESP五级编程竞赛中的“烹饪问题”(对应洛谷B3930)要求从给定的整数数组中寻找一个元素,使其与数组中其他任意元素的按位与(AND)结果最大。题目需在保证结果尽可能大的同时,...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。