2023年12月GESP真题及题解(C++八级): 大量的工作沟通

2023年12月GESP真题及题解(C++八级): 大量的工作沟通

题目描述

某公司有 N N N 名员工,编号从 0 0 0 至 N − 1 N-1 N−1。其中,除了 0 0 0 号员工是老板,其余每名员工都有一个直接领导。我们假设编号为 i i i 的员工的直接领导是 f i f_i fi。

该公司有严格的管理制度,每位员工只能受到本人或直接领导或间接领导的管理。具体来说,规定员工 x x x 可以管理员工 y y y,当且仅当 x = y x=y x=y,或 x = f y x=f_y x=fy,或 x x x 可以管理 f y f_y fy。特别地, 0 0 0 号员工老板只能自我管理,无法由其他任何员工管理。

现在,有一些同事要开展合作,他们希望找到一位同事来主持这场合作,这位同事必须能够管理参与合作的所有同事。如果有多名满足这一条件的员工,他们希望找到编号最大的员工。你能帮帮他们吗?

输入格式

第一行一个整数 N N N ,表示员工的数量。

第二行 N − 1 N-1 N−1 个用空格隔开的正整数,依次为 f 1 , f 2 , ... f N − 1 f_1, f_2, \dots f_{N-1} f1,f2,...fN−1。

第三行一个整数 Q Q Q ,表示共有 Q Q Q 场合作需要安排。

接下来 Q Q Q 行,每行描述一场合作:开头是一个整数 m m m( 2 ≤ m ≤ N 2 \leq m \leq N 2≤m≤N),表示参与本次合作的员工数量;接着是 m m m 个整数,依次表示参与本次合作的员工编号(保证编号合法且不重复)。

保证公司结构合法,即不存在任意一名员工,其本人是自己的直接或间接领导。

输出格式

输出 Q Q Q 行,每行一个整数,依次为每场合作的主持人选。

输入输出样例 1
输入 1
复制代码
5
0 0 2 2
3
2 3 4
3 2 3 4
2 1 4
输出 1
复制代码
2
2
0
输入输出样例 2
输入 2
复制代码
7
0 1 0 2 1 2
5
2 4 6
2 4 5
3 4 5 6
4 2 4 5 6
2 3 4
输出 2
复制代码
2
1
1
1
0
说明/提示

样例解释 1

对于第一场合作,员工 3 , 4 3,4 3,4 有共同领导 2 2 2 ,可以主持合作。

对于第二场合作,员工 2 2 2 本人即可以管理所有参与者。

对于第三场合作,只有 0 0 0 号老板才能管理所有员工。

数据范围

对于 25 % 25\% 25% 的测试点,保证 N ≤ 50 N \leq 50 N≤50。

对于 50 % 50\% 50% 的测试点,保证 N ≤ 300 N \leq 300 N≤300。

对于所有测试点,保证 3 ≤ N ≤ 10 5 3 \leq N \leq 10^5 3≤N≤105, Q ≤ 100 Q \leq 100 Q≤100, m ≤ 10 4 m \leq 10^4 m≤104

思路分析

  1. 问题本质:给定一棵树(根为0),每个查询给出一个节点集合,需要找到这些节点的所有公共祖先中编号最大的一个。
  2. 关键转换
    • 节点 x 是节点 y 的祖先当且仅当 x 在从 y 到根的路径上。
    • 因此,集合 S 的所有公共祖先就是 S 中所有节点到根路径的交集,这个交集恰好是从 S 的最近公共祖先(LCA)到根的路径上的所有节点。
    • 问题转化为:先求 S 中所有节点的 LCA,然后求从 LCA 到根路径上编号最大的节点。
  3. 算法设计
    • 预处理 :使用 BFS 从根开始遍历整棵树,计算每个节点的深度、倍增祖先表以及从该节点到根路径上的最大编号(mx 数组)。
    • 查询处理 :对于每个查询,依次计算所有节点的 LCA(通过两两计算),然后输出 mx[LCA]
  4. 复杂度
    • 预处理:O(N log N) 时间,O(N log N) 空间。
    • 每个查询:O(m log N) 时间,其中 m 是查询的节点数。
    • 总体在题目限制下(N ≤ 1e5,Q ≤ 100,m ≤ 1e4)可以高效运行。
  5. 注意事项
    • 根节点(0)没有父节点,特殊处理。
    • 使用倍增法求 LCA 时,注意处理祖先为 -1 的情况。

代码实现

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100005;
const int LOG = 17;  // 2^17 > 1e5

int n, Q;
int fa[MAXN];            // 父节点,fa[0] = -1
vector<int> g[MAXN];     // 孩子列表
int dep[MAXN];           // 深度
int up[MAXN][LOG];       // 倍增祖先表
int mx[MAXN];            // 从当前节点到根路径上的最大编号

// 预处理:BFS计算深度、倍增表和mx数组
void preprocess() {
    queue<int> q;
    q.push(0);
    dep[0] = 0;
    mx[0] = 0;
    // 初始化根节点的倍增表
    for (int k = 0; k < LOG; ++k) up[0][k] = -1;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int v : g[u]) {
            dep[v] = dep[u] + 1;
            mx[v] = max(v, mx[u]);  // 路径上最大编号
            up[v][0] = u;
            // 计算v的高层祖先
            for (int k = 1; k < LOG; ++k) {
                if (up[v][k-1] != -1)
                    up[v][k] = up[ up[v][k-1] ][k-1];
                else
                    up[v][k] = -1;
            }
            q.push(v);
        }
    }
}

// 求两个节点的最近公共祖先
int lca(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v);
    // 将u提升到与v同一深度
    for (int k = LOG-1; k >= 0; --k) {
        if (up[u][k] != -1 && dep[up[u][k]] >= dep[v]) {
            u = up[u][k];
        }
    }
    if (u == v) return u;
    // 一起向上跳
    for (int k = LOG-1; k >= 0; --k) {
        if (up[u][k] != up[v][k]) {
            u = up[u][k];
            v = up[v][k];
        }
    }
    return up[u][0];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    fa[0] = -1;
    for (int i = 1; i < n; ++i) {
        cin >> fa[i];
        g[fa[i]].push_back(i);
    }

    preprocess();

    cin >> Q;
    while (Q--) {
        int m;
        cin >> m;
        vector<int> s(m);
        for (int i = 0; i < m; ++i) cin >> s[i];
        // 计算所有节点的LCA
        int ans = s[0];
        for (int i = 1; i < m; ++i) {
            ans = lca(ans, s[i]);
        }
        // 输出该LCA到根路径上的最大编号
        cout << mx[ans] << '\n';
    }

    return 0;
}

功能分析

一、数据结构设计
  1. 树存储

    • g[MAXN]:邻接表存储孩子节点
    • fa[MAXN]:父节点数组
    • dep[MAXN]:节点深度
  2. 查询优化结构

    • up[MAXN][LOG]:倍增表,用于快速跳转祖先
    • mx[MAXN]:从根节点到当前节点路径上的最大节点编号
二、核心算法
1. 预处理阶段 (preprocess())
  • 使用BFS遍历整棵树
  • 计算每个节点的深度和倍增祖先表
  • 同时计算mx[]数组:mx[v] = max(v, mx[u])
    • 表示从根到v路径上的最大节点编号
    • 这是一个DP思想,利用了树路径的单调性
2. LCA算法 (lca())
  • 使用倍增法求最近公共祖先
  • 时间复杂度:O(logN)
3. 查询处理

对于每个查询:

  1. 输入m个节点
  2. 计算这些节点的LCA(依次两两计算)
  3. 输出mx[LCA] - 即从根到LCA路径上的最大节点编号
三、时间复杂度总结
  • 预处理:O(NlogN)
  • Q次查询:O(Q·M·logN),其中M是查询集合大小

完整GESP C++考级真题题解专栏:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html

更多csp信奥赛C++学习资料汇总:

1、csp/信奥赛C++,完整信奥赛系列课程(永久学习):

https://edu.csdn.net/lecturer/7901 点击跳转


2、CSP信奥赛C++竞赛拿奖视频课:

https://edu.csdn.net/course/detail/40437 点击跳转

3、csp信奥赛高频考点知识详解及案例实践:

CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转

CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转

信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html

4、csp信奥赛冲刺一等奖有效刷题题解:

CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转

CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转

· 文末祝福 ·

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"跟着王老师一起学习信奥赛C++";
	cout<<"    成就更好的自己!       ";
	cout<<"  csp信奥赛一等奖属于你!   ";
	return 0;
}
相关推荐
郝学胜-神的一滴2 小时前
深入理解TCP协议:数据格式与核心机制解析
linux·服务器·网络·c++·网络协议·tcp/ip
小此方2 小时前
Re:从零开始的 C++ STL篇(三)string的疑难问题详细解析:深拷贝,写时拷贝,三个swap
开发语言·c++
程序员敲代码吗2 小时前
嵌入式C++开发注意事项
开发语言·c++·算法
君义_noip3 小时前
信息学奥赛一本通 1463:门票
c++·算法·哈希算法·信息学奥赛·csp-s
草莓熊Lotso3 小时前
Qt 控件美化与交互进阶:透明度、光标、字体与 QSS 实战
android·java·开发语言·c++·人工智能·git·qt
青山是哪个青山3 小时前
C++ 核心基础与面向对象 (OOP)
开发语言·c++
小明同学013 小时前
[C++进阶]深入理解二叉搜索树
开发语言·c++·git·visualstudio
点云SLAM3 小时前
C++std::enable_if_t 与 std::is_same_v使用
c++·模板元编程·c++ 类型萃取·enable_if_t·is_same_v
C+++Python3 小时前
C++ vector
开发语言·c++·算法