题解:洛谷 P11249 小杨寻宝
【题目来源】
洛谷:P11249 [GESP202409 七级] 小杨寻宝 - 洛谷
【题目描述】
小杨有一棵包含 \(n\) 个节点的树,树上的一些节点放置有宝物。
小杨可以任意选择一个节点作为起点并在树上移动,但是小杨只能经过每条边至多一次,当小杨经过一条边后,这条边就会消失。小杨每经过一个放置有宝物的节点就会取得该宝物。
小杨想请你帮他判断自己能否成功取得所有宝物。
【输入】
本题单个测试点内有多组测试数据。输入第一行包含一个正整数 \(t\),代表测试用例组数。
接下来是 \(t\) 组测试用例。对于每组测试用例,一共 \(n+1\) 行。
第一行包含一个正整数 \(n\),代表树的节点数。
第二行包含 \(n\) 个非负整数 \(a_1, a_2, \dots a_n\),其中如果 \(a_i = 1\),则节点 \(i\) 放置有宝物;若 \(a_i = 0\),则节点 \(i\) 没有宝物。
之后 \(n - 1\) 行,每行包含两个正整数 \(x_i, y_i\),代表存在一条连接节点 \(x_i\) 和 \(y_i\) 的边。
【输出】
对于每组测试数据,如果小杨能成功取得所有宝物,输出 Yes,否则输出 No。
【输入样例】
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
【输出样例】
Yes
No
【核心思想】
-
问题分析:给定一棵 \(n\) 个节点的树,部分节点有宝物。小杨从某节点出发,每条边最多经过一次(经过后消失),要求经过所有有宝物的节点。判断是否存在这样的路径。这是一个树形DFS + 贪心验证问题,关键在于分析有宝物节点的分布约束:每个非根节点的子树中,若有多于1个"需要访问的宝物分支",则无法在不重复经过边的情况下覆盖。
-
算法选择:
- 树形DFS(后序遍历):自底向上统计每个节点的子树中需要访问的宝物节点数量
- 贪心约束验证:对每个节点,检查其子树中需要访问的分支数是否超过限制(根节点最多2个分支,非根节点最多1个分支)
-
关键步骤:
- 读入数据:\(t\) 组测试用例,每组读入 \(n\)、宝物数组 \(a[1..n]\)、\(n-1\) 条边
- 找根节点:任选一个 \(a_i = 1\) 的节点作为根(若没有宝物则 trivially Yes)
- DFS 自底向上处理(参数 \(u\) 当前节点,\(fa\) 父节点):
res= 子树中需要访问的宝物节点之和- 遍历子节点 \(j\)(跳过父节点):
- 递归
dfs(j, u) res += a[j](子节点若有宝物或需要经过,则计入)
- 递归
- 若
res > 0,则a[u] = 1(当前节点需要被经过) - 约束检查:
- 根节点:若
res > 2,flag = false(根最多连接2个需要访问的分支) - 非根节点:若
res > 1,flag = false(非根节点最多连接1个需要访问的分支)
- 根节点:若
- 输出
flag ? "Yes" : "No"
-
时间/空间复杂度:
- 时间复杂度:\(O(t \cdot n)\),每组数据一次DFS遍历整棵树
- 空间复杂度:\(O(n)\),邻接表和递归栈
-
树形DFS贪心约束的核心思想:
- 边消失约束的本质:每条边只能走一次,意味着从父节点进入子树后,若子树中有多个分散的宝物,无法在不重复走边的情况下全部收集
- 分支数限制:非根节点最多只能有1个"需要深入访问的子分支",否则进入该分支后无法再去另一个分支(边已消失)。根节点可以有2个(作为起点和终点)
- 自底向上传播:子树中若有宝物需要收集,则父节点必须经过。
a[u]被置1表示该节点是"必须经过的节点" - 贪心验证的正确性:若某节点违反分支数限制,则不存在合法路径;若所有节点都满足,则可以构造一条欧拉路径式的遍历
- 适用于"树上一笔画覆盖指定节点"的可行性判断问题
【解题思路】

【算法标签】
普及 #DFS-树
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = N * 2;
int T; // 测试用例数量
int n, a[N], root; // n: 节点数, a[i]: 节点i的初始值, root: 根节点
int h[N], e[M], ne[M], idx; // 邻接表存储树
bool flag; // 标志位,表示当前树是否合法
// 添加无向边
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
// 深度优先搜索
// u: 当前节点
// fa: 父节点
void dfs(int u, int fa)
{
if (!flag) return; // 如果已经发现不合法,直接返回
int res = 0; // 记录子节点值的和
// 遍历所有子节点
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i]; // 子节点
if (j == fa) // 跳过父节点
{
continue;
}
// 递归处理子节点
dfs(j, u);
// 累加子节点的值
res += a[j];
}
// 如果子节点的和不为0,将当前节点设为1
if (res)
{
a[u] = 1;
}
// 检查约束条件
if ((u == root && res > 2) || (u != root && res > 1))
{
flag = 0; // 不满足条件,标记为非法
}
}
int main()
{
cin >> T; // 读取测试用例数
while (T--)
{
cin >> n; // 读取节点数
// 初始化邻接表
memset(h, -1, sizeof(h));
idx = 0;
root = 0;
flag = 1; // 初始假设树是合法的
// 读取每个节点的初始值
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 找到值为1的节点作为根节点
for (int i = 1; i <= n; i++)
{
if (a[i] == 1)
{
root = i;
break;
}
}
// 读取树的边
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
// 从根节点开始DFS
dfs(root, 0);
// 输出结果
if (flag)
{
cout << "Yes" << endl;
}
else
{
cout << "No" << endl;
}
}
return 0;
}
【运行结果】
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
Yes
3 4
3 5
No
浙公网安备 33010602011771号