P10109 [GESP202312 六级] 工作沟通

记录118

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=310;
int fa[MAXN],dep[MAXN];//fa[i]记录i的直接领导(父节点),dep[i]记录i的深度(层级)
bool vis[MAXN];//DFS遍历时用来标记节点是否被访问过
vector<int> ch[MAXN];//ch[i]记录i的所有直接下属(子节点) 
int getDep(int x){// 递归计算节点 x 的深度(根节点0的深度为0)
	return x==0?0:getDep(fa[x])+1;
}
void dfs(int x){// DFS深度优先遍历:标记节点 x 以及 x 的所有下属(即 x 的整棵子树)
	vis[x]=1;
	for(int y:ch[x]) dfs(y);//for循环的简写方式 
}
// 检查节点 x 是否能管理 vec 中的所有参与者
// 逻辑:如果 x 能管理所有人,那么 vec 里的每个人一定都在 x 的子树(管辖范围)里
bool check(int x,int n,const vector<int> &vec){// 加上 const:既防止了函数内部误改数据,又配合 & 避免了数据拷贝,是更专业、更高效的写法。 
	for(int i=0;i<=n;i++) vis[i]=0;// 每次检查前,先把标记数组清空
	dfs(x);// 标记 x 能管理的所有人
	for(int y:vec){
		if(vis[y]==0) return 0;
	}// 遍历所有参与者,如果有任何一个人没被标记,说明 x 管不了他
	return 1; // x 能管理所有人
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin>>n;//输入员工数量 	
	for(int i=1;i<n;i++){
		cin>>fa[i];// 读入领导关系并建树 
		ch[fa[i]].push_back(i);// 把 i 加入到 fa[i] 的下属列表中
	}
	for(int i=1;i<n;i++) dep[i]=getDep(i);// 预处理计算出每个节点的深度
	int q;
	cin>>q;// 需要安排的合作场数 
	while(q--){
		int m,minD=n+1;// minD用来记录当前组参与者中的最小深度(职位最高者的深度)
		cin>>m;// 参与本次合作的员工数量 
		vector<int> vec(m);// 构建m大小的数组vec 
		for(int i=0;i<m;i++){
			cin>>vec[i];// 读入参与者
			minD=min(minD,dep[vec[i]]);//  找出其中深度最浅的那个值(minD)
		}
		// 倒序枚举节点(从编号大到编号小),寻找符合要求的主持人
		for(int i=n-1;i>=0;i--){
			// 剪枝与判断:
			// 1. dep[i] <= minD:领导 i 的深度必须小于等于参与者中最浅的深度(领导必须在最上面)
			// 2. check(i, n, vec):领导 i 必须能管理 vec 中的所有人
			if(dep[i]<=minD&&check(i,n,vec)){
				cout<<i<<"\n";
				break;
			}
		}
	}
	return 0;//结束程序 
}

前言

我是一名专注信奥赛(CSP-J/S、NOIP)的教练。

  • 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
  • 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
  • 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。

题目传送门https://www.luogu.com.cn/problem/P10109


突破口

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

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

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


思路

1. 建模:将公司层级转化为"树"

  • 节点与边 :每个员工是一个节点,直接领导关系构成了一条有向边(从父节点指向子节点)。老板 0 是整棵树的根节点。
  • 管理关系 :题目规定"x 可以管理 y,当且仅当 x=y,或 x=fy,或 x 可以管理 fy"。这在图论中的意思是:如果 x 能管理 y,那么 y 一定在 x 的子树中

2. 寻找主持人的逻辑

  • 主持人必须能管理所有参与合作的员工,这意味着所有参与者都必须在主持人的子树内
  • 如果有多个满足条件的人,要选编号最大的。

3. 算法设计:预处理 + 暴力枚举剪枝

由于数据范围较小( N≤300,Q≤100N≤300,Q≤100 ),我们可以采用以下策略:

  • 建树与求深度:记录每个人的直接领导和下属,并算出每个人在公司的"层级(深度)"。
  • 寻找候选人的上限 :如果一个主持人能管理所有人,他的职位必须大于等于 所有人中最高的那个职位(即深度最浅的那个人)。因此,我们只需要检查深度 ≤≤ 参与者最小深度的那些节点即可。
  • 倒序遍历找答案 :为了满足"编号最大"的要求,我们从 N−1N−1 到 00 倒着遍历节点。一旦找到第一个合法的候选人,直接输出并 break
  • DFS 验证管辖权:对于当前枚举到的候选人,通过一次 DFS 标记他子树里的所有人,然后核对参与者是否全被标记。

代码分析

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=310;
int fa[MAXN],dep[MAXN]; // fa[i]记录i的直接领导(父节点),dep[i]记录i的深度(层级)
bool vis[MAXN];         // DFS遍历时用来标记节点是否被访问过
vector<int> ch[MAXN];   // ch[i]记录i的所有直接下属(子节点) 

解析

  • 定义了全局变量和常量。使用 vector 数组 ch 来构建邻接表,存储树结构中每个节点的子节点列表。
cpp 复制代码
int getDep(int x){ // 递归计算节点 x 的深度(根节点0的深度为0)
	return x==0 ? 0 : getDep(fa[x])+1;
}

解析

  • 利用递归向上追溯父节点来计算深度。如果到了根节点 0,深度返回 0;否则,其深度等于"直接领导的深度 + 1"。
cpp 复制代码
void dfs(int x){ // DFS深度优先遍历:标记节点 x 以及 x 的所有下属(即 x 的整棵子树)
	vis[x]=1;
	for(int y:ch[x]) dfs(y); // for循环的简写方式(C++11特性),遍历x的所有子节点
}

解析

  • 标准的深度优先搜索。进入一个节点就打上 vis[x]=1 的标记,然后对其所有的直接下属递归调用自身。执行完后,所有在 x 管辖范围内的员工都会被标记。
cpp 复制代码
// 检查节点 x 是否能管理 vec 中的所有参与者
// 逻辑:如果 x 能管理所有人,那么 vec 里的每个人一定都在 x 的子树(管辖范围)里
bool check(int x,int n,const vector<int> &vec){ 
    // 加上 const:既防止了函数内部误改数据,又配合 & 避免了数据拷贝,是更专业、更高效的写法。 
	for(int i=0;i<=n;i++) vis[i]=0; // 每次检查前,先把标记数组清空
	dfs(x);                         // 标记 x 能管理的所有人
	for(int y:vec){
		if(vis[y]==0) return 0;     // 遍历所有参与者,如果有任何一个人没被标记,说明 x 管不了他
	}
	return 1;                       // x 能管理所有人
}

解析

  • 这是一个封装好的验证函数。先重置 vis 数组,再跑一遍 dfs(x),最后逐一核对本次合作的人员名单。只要发现一个不在 x 子树内的人,立刻返回 false (0)。
cpp 复制代码
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); // 这两行用于关闭同步流,提高 cin/cout 的输入输出效率,防止超时
	int n;
	cin>>n; // 输入员工数量 	
	for(int i=1;i<n;i++){
		cin>>fa[i];             // 读入领导关系并建树 
		ch[fa[i]].push_back(i); // 把 i 加入到 fa[i] 的下属列表中
	}

解析

  • 读取数据并完成建树操作。注意题目给的是 f1到 fn−1 ,所以循环从 i=1 开始。
cpp 复制代码
	for(int i=1;i<n;i++) dep[i]=getDep(i); // 预处理计算出每个节点的深度
	int q;
	cin>>q; // 需要安排的合作场数 
	while(q--){
		int m,minD=n+1; // minD用来记录当前组参与者中的最小深度(职位最高者的深度)
		cin>>m; // 参与本次合作的员工数量 
		vector<int> vec(m); // 构建m大小的数组vec 
		for(int i=0;i<m;i++){
			cin>>vec[i]; // 读入参与者
			minD=min(minD,dep[vec[i]]); // 找出其中深度最浅的那个值(minD)
		}

解析

  • 处理每一次查询。在读取参与者编号的同时,顺便求出这批人中最小的深度 minD 。这是非常关键的剪枝 依据:任何深度大于 minD 的员工,绝对不可能成为这批人的共同管理者。
cpp 复制代码
		// 倒序枚举节点(从编号大到编号小),寻找符合要求的主持人
		for(int i=n-1;i>=0;i--){
			// 剪枝与判断:
			// 1. dep[i] <= minD:领导 i 的深度必须小于等于参与者中最浅的深度(领导必须在最上面)
			// 2. check(i, n, vec):领导 i 必须能管理 vec 中的所有人
			if(dep[i]<=minD && check(i,n,vec)){
				cout<<i<<"\n";
				break; // 因为是倒序遍历,找到的第一个合法解一定是编号最大的,直接跳出
			}
		}
	}
	return 0; // 结束程序 
}

解析

  • 核心查找逻辑。从 n-1 递减到 0
  • dep[i] <= minD 作为前置条件过滤掉了大量不可能的节点,极大地减少了 check 函数的调用次数。
  • 一旦 check 返回 true,立即输出该节点编号并 break,完美契合题目"找编号最大的员工"的要求。
相关推荐
吴可可1231 小时前
CAD二次开发中多段线定点分割技巧
算法
Xeon_CC1 小时前
vs2026远程开发debian12容器的C++程序笔记
开发语言·c++·笔记
逻极1 小时前
Redis 从入门到精通:缓存设计与实战
数据结构·redis·缓存·哨兵集群
ʚ希希ɞ ྀ1 小时前
全排列 --- 回溯
算法·leetcode·深度优先
玉树临风ives1 小时前
atcoder ABC 460 题解
数据结构·c++·算法
水无痕simon1 小时前
9 C语言的基础练习
c语言·开发语言·算法
少司府1 小时前
C++进阶:二叉搜索树
开发语言·数据结构·c++·二叉树·stl·二叉搜索树·tree
8Qi81 小时前
LeetCode 124. 二叉树中的最大路径和(Hard)
算法·leetcode·二叉树·递归
Huangjin007_1 小时前
【C++ STL篇(十四)】哈希表实现:开放定址法与链地址法
c++·哈希算法·散列表