CF1990E2 抓鼹鼠 解题报告
显然地,考虑点静止的话,反而更难通过“重心”这类方式在老哥复杂度内求解,因此这个向上移动的条件是必要的。
题目给了 \(160\) 次操作,似乎是一个根号做法。尝试下。
然后玩一会可以发现一个大致框架,就是先从树根开始向下搜索,每次进入那个返回结果为 \(1\) 的分支。确保鼹鼠在当前点上面后,可以找到 \(1\) 到当前点的一条链,这样就可以二分了!
但是直接搜显然是不行的,需要通过一些强力的方法先做处理,减掉搜索中部分询问次数。
刚刚说了似乎是个根号做法,那么高度 \(< \sqrt{n}\) 的子树可以直接被排除掉,而为了实现这个,只需要在最开始随便选一个叶子查询 \(\sqrt{n}\) 次就行,好像还挺划算的。
然后刚刚的做法就被加强了:先选一个叶子查询(钦定鼹鼠不在上面) \(\sqrt{n}\) 次,然后往下搜索同时,排除掉高度 \(<\) 查询失败的次数的子树不进行询问就好了。
看看 DFS 内的查询次数是否是 \(\sqrt{n}\) 级别的:首先进行询问的子树大小一定是 \(\ge\) 查询失败次数的。如果不存在一次查询的点是另一次的祖先,总次数就是 \(\sqrt{n}\) 了。但是考虑整棵树是一条链,就不满足这种情况。
这个时候考虑当前遍历的点,它所有满足高度 \(\ge \sqrt{n}\) 的子树集合 \(S\)。从前向后遍历,依次查询,如果没有遍历完时,就已经找到一个返回 \(1\) 的了,那么后面的就不用遍历了,这个元素使用的查询机会,可以算在后面还没有遍历的元素头上。如果返回 \(1\) 的是最后一个呢?因为前面的肯定全部是 \(0\),这时候如果最后一个子树高度 \(\ge\) 查询失败次数,鼹鼠就一定在最后一个子树里了(否则代表鼹鼠已经在当前点上面了,退出 DFS),那么最后一个就不用消耗查询次数了。
重新规划后,满足了不存在一次查询的点是另一次的祖先,然后总查询次数就是 \(2\sqrt{n} + \log{n}\)。二分因为鼹鼠会移动,有些细节注意下就行。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5007;
int n, dep[N], h[N], lf, ed, B, fa[N];
vector<int> g[N], res;
int ask(int x){
cout << "? " << x << endl;
int res; cin >> res;
if(!res) B ++;
return res;
}
void cal(int u, int pre){
fa[u] = pre, h[u] = dep[u];
int son = 0;
for(int v: g[u]){
if(v != pre){
dep[v] = dep[u] + 1;
cal(v, u), son ++;
h[u] = max(h[u], h[v]);
}
}
if(!son) lf = u;
}
void dfs(int u, int pre){
res.clear();
for(int v: g[u]){
if(v == pre) continue;
if(h[v] - dep[v] < B) continue;
res.push_back(v);
}
if(res.empty()){
ed = u;
return;
}
for(int i = 0; i + 1 < res.size(); i ++){
if(h[res[i]] - dep[res[i]] >= B && ask(res[i])){
dfs(res[i], u);
return;
}
}
int v = res.back();
if(h[v] - dep[v] < B){
ed = u;
return;
}
dfs(v, u);
}
void solve(){
cin >> n;
for(int i = 1; i <= n; i ++){
g[i].clear();
}
for(int i = 1; i < n; i ++){
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
cal(1, 0);
B = 0;
int V = sqrt(n);
if(ask(lf)){
cout << "! " << lf << endl;
return;
}
for(int i = 1; i < V; i ++) ask(lf);
dfs(1, 0);
vector<int> pth;
while(1){
pth.push_back(ed);
if(ed == 1) break;
ed = fa[ed];
}
pth.push_back(0);
reverse(pth.begin(), pth.end());
int l = 0, r = pth.size();
while(l + 1 < r){
int mid = (l + r) >> 1;
if(ask(pth[mid])) l = mid;
else{
r = mid;
l = max(1ll, l - 1);
r = max(l + 1, r - 1);
}
}
cout << "! " << pth[l] << endl;
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0);
int t; cin >> t;
while(t --) solve();
return 0;
}

浙公网安备 33010602011771号