1 条题解
-
0
C. 特拉普米贾诺·雷贾诺(陷阱奶酪)详细题解
一、题目理解
有一棵 个顶点的树。一只老鼠从起点 出发。
我们有一个长度为 的排列 ,表示奶酪出现的顺序。
第 步:- 奶酪出现在顶点
- 如果老鼠当前在 ,则不动
- 否则,老鼠沿着到 的最短路径移动 一条边
目标:找到排列 ,使得 步后老鼠必定在陷阱顶点 。
二、核心观察
- 树的性质:任意两点之间有唯一简单路径。
- 老鼠移动规则:每次奶酪出现时,老鼠向其移动一步(若已在则不动)。
- 关键想法:如果按离 从远到近的顺序让奶酪出现,老鼠会被一步步“拉”向 。
- 最后一步:当奶酪出现在 本身时,老鼠一定已经在 (因为其他所有顶点都已出现过,老鼠已被引导至此)。
三、算法步骤
设以 为根,定义 为顶点 到 的距离(边数)。
- 以 为根,对树进行 DFS,计算每个顶点的深度。
- 将深度相同的顶点放入同一个桶中。
- 按深度从大到小依次输出所有顶点。
为什么这样构造有效?
- 深度最大的顶点距离 最远,先出现。
- 老鼠从 出发,每次奶酪出现在深度较大的顶点时,它会向该顶点移动一步(实际是向 靠近,因为深度大的顶点在 的远处)。
- 当所有深度 的顶点都出现过后,最后出现 本身(深度 )。
- 此时老鼠一定在 ,因为前面的移动已将其引导至根。
四、正确性证明
引理:按照深度降序输出顶点,老鼠每步后与 的距离单调不增,且当所有非 顶点输出后,老鼠在 。
证明:
- 设当前老鼠在顶点 ,奶酪出现在 。
- 若 ,老鼠向 移动一步(或已在)。
- 若 ,由于 的深度 (因为深度大的先出现),老鼠向 移动一步,实际是向 靠近(因为 在 的某个子树中)。
- 最终,当所有深度 的顶点都出现后,老鼠必然在深度 的顶点,即 。
五、标程代码解析
#include <bits/stdc++.h> using namespace std; void solve() { int n, s, e; cin >> n >> s >> e; // 邻接表 vector<vector<int>> adj(n + 1); for (int i = 1; i < n; i++) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); } // dis[d] 存放深度为 d 的所有顶点 vector<vector<int>> dis(n + 1); vector<int> d(n + 1, 0); // d[v] = 深度,d[0]=0 是虚拟根 // DFS 以 e 为根,计算深度并分组 auto dfs = [&](auto &&self, int v, int par) -> void { d[v] = d[par] + 1; // 深度 = 父节点深度 + 1 dis[d[v]].push_back(v); // 按深度分组 for (int u : adj[v]) { if (u == par) continue; self(self, u, v); } }; dfs(dfs, e, 0); // 从 e 开始,父节点为 0(虚拟根) // 按深度从大到小输出 for (int i = n; i >= 1; i--) { for (int j : dis[i]) { cout << j << " "; } } cout << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int tt; cin >> tt; while (tt--) { solve(); } return 0; }
六、复杂度分析
- 时间: 每个测试用例(DFS 遍历 + 输出)
- 空间:
- 满足 的限制
七、示例验证
输入:
4 1 1 1 2 1 2 1 2 3 2 2 1 2 2 3 6 1 4 1 2 1 3 4 5 5 6 1 4运行过程(以第 3 个测试用例为例,):
- 以 为根:
- 按深度降序:深度 的顶点 ,深度 的顶点
- 输出:
输出:
1 2 1 3 1 2 1 4 3 2 6 5与题目示例一致 ✅
八、总结
要点 说明 核心思想 以 为根,按深度降序输出顶点 算法类型 DFS + 贪心构造 时间复杂度 空间复杂度 正确性 老鼠每步向 靠近,最后必达
- 1
信息
- ID
- 7151
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 5
- 标签
- 递交数
- 12
- 已通过
- 1
- 上传者