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

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

5天前

洛谷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的场景。代码结构清晰,预处理与查询分离,可扩展性强。理解此解法有助于优化树结构相关算法设计,提升解题效率。


原创内容 转载请注明出处

分享给朋友:

相关文章

力扣35:二分法在搜索插入位置中的运用

力扣35:二分法在搜索插入位置中的运用

有序数组的定位在一个严格递增的数字序列中,每个元素都有其确定的位置。当新元素试图加入时,我们需要回答两个问题:它是否已经存在?如果不存在,它应该插入在哪里?这道题要求我们在O(log n)时间内完成这...

2012年NOIP提高组「借教室」题目(P1083)解题思路与二分查找优化代码解析

2012年NOIP提高组「借教室」题目(P1083)解题思路与二分查找优化代码解析

一、题目解读本题为2012年NOIP提高组中的「借教室」问题(洛谷P1083),要求处理教室借用订单的分配问题。给定n天每天可用教室数量r和m个订单(订单包含需求教室数d、开始日期s、结束日期t),判...

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

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

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

牛客13279题解:基于广度优先搜索(BFS)计算树高度的算法优化与代码实现

牛客13279题解:基于广度优先搜索(BFS)计算树高度的算法优化与代码实现

一、题目解读牛客13279题要求计算给定树的高度。树由n个节点组成,通过n-1条边连接,形成一棵有根树。题目核心在于高效求解树的高度,即从根节点到最深叶子节点的最长路径长度。需要理解树的结构特点,选择...

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

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

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

力扣1011题详解:船只装载问题的二分查找优化解法(C++代码实现)

力扣1011题详解:船只装载问题的二分查找优化解法(C++代码实现)

一、题目解读力扣1011题要求在给定的货物重量数组中,计算船只每天最多装载一定重量时,最少需要多少天完成运输。题目关键在于找到满足天数限制的最小最大载重量,属于典型的查找最值问题。二、解题思路采用二分...

发表评论

访客

看不清,换一张

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