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

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,且保证树上至少有一个点放置有宝物。

思路分析

问题本质

这道题的核心是判断树中所有宝物节点是否在一条简单路径上。

关键理解
  1. 移动限制:每条边只能走一次,这意味着路径不能有分支,必须是简单路径
  2. 需要遍历所有宝物节点:但路径必须是线性的
  3. 结论:能成功取得所有宝物 ⇔ 所有宝物节点在一条简单路径上
解决方案
  1. 找到宝物节点的直径

    • 任意选择一个宝物节点作为起点
    • 找到距离该节点最远的宝物节点 u
    • u 出发,找到距离 u 最远的宝物节点 v
    • uv 之间的路径就是宝物节点的直径
  2. 检查所有宝物节点是否在路径 u-v

    • 对于任意节点 i,如果它位于路径 u-v 上,则满足:
      • dist(u,i) + dist(i,v) = dist(u,v)
    • 因此只需要检查每个宝物节点是否满足这个条件
算法步骤
  1. 建树(邻接表)
  2. 找到任意一个宝物节点作为起点
  3. 两次DFS求宝物节点的直径端点 uv
  4. 检查所有宝物节点是否在路径 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;
}

功能分析

核心算法:树直径法判断共线性
  1. 第一次DFS :从任意宝物节点找到最远的宝物节点 u
  2. 第二次DFS :从 u 找到最远的宝物节点 v,确定宝物节点的直径
  3. 第三次DFS :计算所有节点到 v 的距离
  4. 验证 :检查每个宝物节点是否在 u-v 路径上
关键性质
  • 如果所有宝物节点在一条简单路径上,那么这条路径一定是宝物节点形成的"子图"的直径

  • 对于树上的三个点 u, i, v,点 iu-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;
}
相关推荐
凯子坚持 c2 小时前
C++大模型SDK开发实录(一):spdlog日志封装、通用数据结构定义与策略模式应用
数据结构·c++·sdk·策略模式
小屁猪qAq2 小时前
设计模式总纲
开发语言·c++·设计模式
Howrun7772 小时前
C++标准线程库-全面讲解
开发语言·c++
tod1133 小时前
从零手写一个面试级 C++ vector:内存模型、拷贝语义与扩容策略全解析
c++·面试·职场和发展·stl·vector
OopspoO3 小时前
C++杂记——构造函数
c++
淦。。。。3 小时前
题解:P14013 [POCamp 2023] 送钱 / The Generous Traveler
开发语言·c++·经验分享·学习·其他·娱乐·新浪微博
天赐学c语言3 小时前
1.18 - 滑动窗口最大值 && 子类的指针转换为父类的指针,指针的值是否会改变
数据结构·c++·算法·leecode
是娇娇公主~4 小时前
C++集群聊天服务器(3)—— 项目数据库以及表的设计
服务器·数据库·c++
zephyr054 小时前
C++ STL unordered_set 与 unordered_map 完全指南
开发语言·数据结构·c++