CF750F Solution
Preface
咕咕咕咕咕咕咕了半年有余。不得不说这题真的会把你调炸!!!!!!11
以及,首黑,2022 年 7 月 31 日 15:51。
Solution
算法一
询问次数
- Hint 0:请注意,你必须设计一个确定性的算法,即使随机化算法只有非常小的几率使用超过
次询问。 - Hint 1:根节点的度数为
。 - Hint 2:在所有根节点备选方案中,如已确定其余所有都不是根节点,会如何?
对
算法二
询问次数
下文的「上」指的是向深度低的节点搜索,反之「下」指的是向深度高的节点搜索。
- Hint 3:一条从
出发最后到达叶子节点的路径,一定是先向上走到与 的任意一个祖先(包括 ),再掉头一直向下走。 - Hint 4:两个节点的深度相同,则它们的
在它们的路径中点上。
取一个初始点
- 如果我们找到的两条路径的
是 ,那么相当于我们确定了 的三条连边中向下的两条路径。所以我们可以得出 的父亲。如果 的度数为 ,则可以跳过这一步。 - 否则,可以证明这个新的
是 的祖先。而对于这个节点,同样有我们知道了其三条连边中向下的两条路径。
对于新的节点,直接从其上方搜索一条路径即可。
综上所述,我们可以一直迭代直到找到度数为
这样做的最劣复杂度是什么?可以看一张
容易证明这是最劣情况,共需询问
考虑优化。
- Hint 5:最小的最劣会超出
次询问次数的深度为?
- 答案是
,询问次数分别为: 。
根据这个 Hint,我们考虑起始深度
如果起始深度
- 剪枝:如果
且知道了 lca 深度为 ,直接给它跳到深度为 的节点(其它深度都不需要特判,手玩一下即可)。
Code
#include <bits/stdc++.h>
constexpr int MaxNode = 127;
std::vector<int> neighbours[MaxNode + 1];
int depth[MaxNode + 1];
int query(int u) {
if (!neighbours[u].empty()) return neighbours[u].size();
std::cout << "? " << u << std::endl;
static int ans, p; std::cin >> ans;
if (ans == 0) exit(0);
while (ans--) {
std::cin >> p;
neighbours[u].push_back(p);
}
return neighbours[u].size();
}
void judge(int u) {
std::cout << "! " << u << std::endl;
for (int i = 1; i <= MaxNode; ++i)
neighbours[i].clear();
memset(depth, 0, sizeof(depth));
}
std::vector<int> lcapath(std::vector<int> A, std::vector<int> B, int H) {
std::reverse(A.begin(), A.end());
int N = (A.size() + B.size()) / 2;
std::vector<int> ans;
for (int i = 1; i < B.size(); ++i) A.push_back(B[i]);
for (int i = 0; i < A.size(); ++i)
depth[A[i]] = H - std::min(i, (int)A.size() - i - 1);
for (int i = 0; i < N; ++i) ans.push_back(A[i]);
std::reverse(ans.begin(), ans.end());
return ans;
}
constexpr int Start_Point = 1;
void interactive() {
int H, current = Start_Point;
std::cin >> H;
if (H == 0) exit(0);
std::vector<int> pathA, pathB;
pathA.push_back(current), pathB.push_back(current);
while (query(pathA.back()) != 1) {
if (query(pathA.back()) == 2)
return (void)judge(pathA.back());
for (auto u : neighbours[pathA.back()])
if (neighbours[u].empty()) {
pathA.push_back(u);
break;
}
}
while (query(pathB.back()) != 1) {
if (query(pathB.back()) == 2)
return (void)judge(pathB.back());
for (auto u : neighbours[pathB.back()])
if (neighbours[u].empty()) {
pathB.push_back(u);
break;
}
}
if (pathA.size() == 1 && pathB.size() == 1) {
pathA.push_back(neighbours[current][0]);
pathB.push_back(neighbours[current][0]);
std::reverse(pathA.begin(), pathA.end());
std::reverse(pathB.begin(), pathB.end());
}
pathA = lcapath(pathA, pathB, H);
pathB = std::vector<int>(1, pathA.front());
while (true) {
if (depth[pathA.front()] == 4 && H == 7) {
std::reverse(pathA.begin(), pathA.end());
for (auto u : neighbours[pathA.back()])
if (neighbours[u].empty())
pathA.push_back(u), depth[u] = 3;
std::reverse(pathA.begin(), pathA.end());
}
if (depth[pathA.front()] == 3 && H == 7) {
query(pathA.front());
std::vector<int> sel;
for (auto u : neighbours[pathA.front()]) {
query(u); for (auto v : neighbours[u])
if (neighbours[v].empty()) sel.push_back(v);
}
for (auto i : sel) if (i == sel.back())
return (void)judge(i);
else if (query(i) == 2) return (void)judge(i);
exit(-1);
}
if (depth[pathA.front()] == 2 && H == 7) {
query(pathA.front());
std::vector<int> sel;
for (auto u : neighbours[pathA.front()])
if (neighbours[u].empty()) sel.push_back(u);
for (auto i : sel) if (i == sel.back())
return (void)judge(i);
else if (query(i) == 2) return (void)judge(i);
exit(-2);
}
while (query(pathB.back()) != 1) {
if (query(pathB.back()) == 2)
return (void)judge(pathB.back());
for (auto u : neighbours[pathB.back()])
if (neighbours[u].empty()) {
pathB.push_back(u);
break;
}
}
pathA = lcapath(pathA, pathB, H);
pathB = std::vector<int>(1, pathA.front());
}
}
int main() {
int T; std::cin >> T;
while (T--) interactive();
return 0;
}
Postscript
算法三
基于其他题解的思路:以一个点作为起始点,bfs 搜索。
- 这样做的好处是,在深度小的时候可以明显减小搜索次数。
- 但是,当深度越来越大时,需要代码实现趋近于 dfs。
- 这也是原有的题解做法存在的问题:如果我把
开大一点,你不得不考虑写一大堆剪枝来达到模拟 dfs 的效果。
代码实现本来是有的,但是我调炸了,一开始写的就是这种做法结果调到吐血最后不得已自己独立推出了官方题解做法(也就是这里的算法二),所以看其他题解去吧。
算法四
来自于 cnyz,他后来隐藏了博客。
大概就是对于每个
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】