2024年9月GESP真题及题解(C++七级): 小杨寻宝

题目描述
小杨有一棵包含 n n n 个节点的树,树上的一些节点放置有宝物。
小杨可以任意选择一个节点作为起点并在树上移动,但是小杨只能经过每条边至多一次,当小杨经过一条边后,这条边就会消失。小杨每经过一个放置有宝物的节点就会取得该宝物。
小杨想请你帮他判断自己能否成功取得所有宝物。
输入格式
本题单个测试点内有多组测试数据 。输入第一行包含一个正整数 t t t,代表测试用例组数。
接下来是 t t t 组测试用例。对于每组测试用例,一共 n + 1 n+1 n+1 行。
第一行包含一个正整数 n n n,代表树的节点数。
第二行包含 n n n 个非负整数 a 1 , a 2 , ... a n a_1, a_2, \dots a_n a1,a2,...an,其中如果 a i = 1 a_i = 1 ai=1,则节点 i i i 放置有宝物;若 a i = 0 a_i = 0 ai=0,则节点 i i i 没有宝物。
之后 n − 1 n - 1 n−1 行,每行包含两个正整数 x i , y i x_i, y_i xi,yi,代表存在一条连接节点 x i x_i xi 和 y i y_i yi 的边。
输出格式
对于每组测试数据,如果小杨能成功取得所有宝物,输出 Yes,否则输出 No。
输入输出样例 1
输入 1
2
5
0 1 0 1 0
1 2
1 3
3 4
3 5
5
1 1 1 1 1
1 2
1 3
3 4
3 5
输出 1
Yes
No
说明/提示
样例 1 解释
对于第一组测试用例,小杨从节点 2 2 2 出发,按照 2 − 1 − 3 − 4 2-1-3-4 2−1−3−4 的顺序即可成功取得所有宝物。
数据规模与约定
| 子任务编号 | 数据点占比 | t t t | n n n |
|---|---|---|---|
| 1 1 1 | 20 % 20\% 20% | ≤ 10 \leq 10 ≤10 | ≤ 5 \leq 5 ≤5 |
| 2 2 2 | 20 % 20\% 20% | ≤ 10 \leq 10 ≤10 | ≤ 10 3 \leq 10^3 ≤103 |
| 3 3 3 | 60 % 60\% 60% | ≤ 10 \leq 10 ≤10 | ≤ 10 5 \leq 10^5 ≤105 |
对全部的测试点,保证 1 ≤ t ≤ 10 1 \leq t \leq 10 1≤t≤10, 1 ≤ n ≤ 10 5 1 \leq n \leq 10^5 1≤n≤105, 0 ≤ a i ≤ 1 0 \leq a_i \leq 1 0≤ai≤1,且保证树上至少有一个点放置有宝物。
思路分析
问题本质
这道题的核心是判断树中所有宝物节点是否在一条简单路径上。
关键理解
- 移动限制:每条边只能走一次,这意味着路径不能有分支,必须是简单路径
- 需要遍历所有宝物节点:但路径必须是线性的
- 结论:能成功取得所有宝物 ⇔ 所有宝物节点在一条简单路径上
解决方案
-
找到宝物节点的直径:
- 任意选择一个宝物节点作为起点
- 找到距离该节点最远的宝物节点
u - 从
u出发,找到距离u最远的宝物节点v u和v之间的路径就是宝物节点的直径
-
检查所有宝物节点是否在路径
u-v上:- 对于任意节点
i,如果它位于路径u-v上,则满足:dist(u,i) + dist(i,v) = dist(u,v)
- 因此只需要检查每个宝物节点是否满足这个条件
- 对于任意节点
算法步骤
- 建树(邻接表)
- 找到任意一个宝物节点作为起点
- 两次DFS求宝物节点的直径端点
u和v - 检查所有宝物节点是否在路径
u-v上
代码实现
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10; // 最大节点数
// 邻接表存储树
vector<int> g[N];
// a[]: 记录每个节点是否有宝物
// d1[]: 记录从节点u到其他节点的距离
// d2[]: 记录从节点v到其他节点的距离
int a[N], d1[N], d2[N];
int n; // 节点数
int u, v; // 宝物节点直径的两个端点
int mx; // 记录当前搜索中的最远距离
/* DFS计算距离
* u 当前节点
* fa 父节点(避免回环)
* dist 从起点到当前节点的距离
* d[] 距离数组,记录从起点到每个节点的距离
*
* 同时更新最远的宝物节点
*/
void dfs(int u, int fa, int dist, int d[]) {
d[u] = dist; // 记录距离
// 如果当前节点有宝物,且距离比当前最远距离更大
if (a[u] && dist > mx) {
mx = dist; // 更新最远距离
v = u; // 记录最远节点(参数v是全局变量,会被修改)
}
// 遍历所有邻居节点
for (int x : g[u]) {
if (x != fa) { // 避免回到父节点
dfs(x, u, dist + 1, d);
}
}
}
//solve函数:处理单个测试用例
void solve() {
cin >> n;
// 初始化:清空邻接表,读取宝物信息
for (int i = 1; i <= n; i++) {
cin >> a[i];
g[i].clear();
}
// 建立树的邻接表
for (int i = 1; i < n; i++) {
int x, y;
cin >> x >> y;
g[x].push_back(y);
g[y].push_back(x);
}
// 找到第一个宝物节点作为起点
int s = 1;
while (s <= n && !a[s]) s++;
// 题目保证至少有一个宝物节点,所以s一定存在
// 第一步:从s出发,找到最远的宝物节点u
mx = -1; // 初始化最远距离
dfs(s, 0, 0, d1); // 计算从s到所有节点的距离
u = v; // v现在是最远的宝物节点
// 第二步:从u出发,找到最远的宝物节点v
// 此时u-v的路径就是宝物节点的直径
mx = -1;
dfs(u, 0, 0, d1); // 重新计算从u到所有节点的距离,记录在d1中
int t = v; // v现在是另一个端点,暂存为t
// 第三步:从t出发,计算到所有节点的距离,记录在d2中
dfs(t, 0, 0, d2);
// 检查所有宝物节点是否在u-t路径上
for (int i = 1; i <= n; i++) {
if (a[i]) { // 只检查宝物节点
// 判断节点i是否在路径u-t上:
// 如果dist(u,i) + dist(i,t) == dist(u,t),则i在u-t路径上
if (d1[i] + d2[i] != d1[t]) {
cout << "No\n";
return;
}
}
}
cout << "Yes\n";
}
int main() {
// 优化输入输出
ios::sync_with_stdio(false);
cin.tie(0);
int T; // 测试用例组数
cin >> T;
while (T--) {
solve();
}
return 0;
}
功能分析
核心算法:树直径法判断共线性
- 第一次DFS :从任意宝物节点找到最远的宝物节点
u - 第二次DFS :从
u找到最远的宝物节点v,确定宝物节点的直径 - 第三次DFS :计算所有节点到
v的距离 - 验证 :检查每个宝物节点是否在
u-v路径上
关键性质
-
如果所有宝物节点在一条简单路径上,那么这条路径一定是宝物节点形成的"子图"的直径
-
对于树上的三个点
u, i, v,点i在u-v路径上当且仅当:dist(u,i) + dist(i,v) = dist(u,v)
时间复杂度
- 每次DFS: O(n)
- 三次DFS: O(3n) = O(n)
- 总复杂度: O(t*n),其中t≤10,n≤ 10 5 10^5 105
空间复杂度
- 邻接表: O(n)
- 距离数组: O(n)
- 总复杂度: O(n)
各种学习资料,助力大家一站式学习和提升!!!
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"########## 一站式掌握信奥赛知识! ##########";
cout<<"############# 冲刺信奥赛拿奖! #############";
cout<<"###### 课程购买后永久学习,不受限制! ######";
return 0;
}
1、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
2、csp信奥赛冲刺一等奖有效刷题题解:
CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转
CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转
3、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
4、CSP信奥赛C++竞赛拿奖视频课:
https://edu.csdn.net/course/detail/40437 点击跳转

· 文末祝福 ·
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"跟着王老师一起学习信奥赛C++";
cout<<" 成就更好的自己! ";
cout<<" csp信奥赛一等奖属于你! ";
return 0;
}