题意
交互题。 给你一棵 n n n 个点的树,需要得出树上两个点 X , Y X,Y X,Y 的位置。你可以向评测机询问一个点集,评测机会回答点集中与 X , Y X,Y X,Y 距离和最小的点,以及这个距离和。询问不超过 11 11 11 次。
思路
询问次数不能超过 11 11 11 次,这个数字与 log n \log n logn 的值很接近。考虑先对所有 n n n 个点进行询问,这样就可以得到 X , Y X,Y X,Y 之间的距离,和一个必定在 X , Y X,Y X,Y 路径上的点。设这个点为 r t rt rt, X , Y X,Y X,Y 之间的距离为 d i s dis dis。如果我们以 r t rt rt 为树根,那么对于树中深度为 d e p dep dep 的点集 { A } \{A\} {A},且 d e p dep dep 大于等于 X , Y X,Y X,Y 中较小的深度,我们对其进行询问:
- 如果询问得到的距离和为 d i s dis dis,则表示询问得到的点在 X , Y X,Y X,Y 的路径上,即 X , Y X,Y X,Y 中较大的深度大于等于 d e p dep dep;
- 如果询问得到的距离和大于 d i s dis dis,则表示该深度的点中没有在 X , Y X,Y X,Y 的路径上的,即 X , Y X,Y X,Y 中较大深度小于 d e p dep dep。
对于第一种情况询问到的点都可能是 X , Y X,Y X,Y 中深度较大的点,不妨令 d e p X ≥ d e p Y dep_X\ge dep_Y depX≥depY。考虑二分,区间为 [ ⌊ d i s 2 ⌋ , min ( d i s , D e p ) ] [\lfloor\cfrac{dis}{2}\rfloor,\min(dis,Dep)] [⌊2dis⌋,min(dis,Dep)],其中 D e p Dep Dep 为最大深度。每一轮都询问一次深度为 m i d mid mid 的点集,按照上述两种情况二分,最终可以得到深度较大的 X X X。然后我们以 X X X 为树根,然后询问深度为 d i s dis dis 的点集,得到的点即为 Y Y Y。
为什么二分区间上界可以这么取?
感性理解一下,因为我们要处理出 X , Y X,Y X,Y 较深的一个点,所以深度小于 d i s ( X , Y ) dis(X,Y) dis(X,Y) 的点都不会是那个较深的点。
实现
实现没什么难点,注意每次询问/回答都要 fflush(stdout)
。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
vector<int> mp[maxn << 1];
void addEdge(int u,int v) {
mp[u].push_back(v);
}
vector<int> a[maxn]; int Dep;
void dfs(int u,int fa,int dep) {
a[dep].push_back(u);
Dep = max(Dep,dep);
for (auto v : mp[u]) {
if (v == fa) continue;
dfs(v,u,dep + 1);
}
}
pair<int,int> Query(const vector<int> &ask) {
printf("? %d",ask.size());
for (auto x : ask) printf(" %d",x);
puts("");
fflush(stdout);
int x,d;
scanf("%d %d",&x,&d);
return make_pair(x,d);
}
pair<int,int> Solve(int n,const vector<pair<int,int> > &edge) {
for (int i = 1;i <= n;i ++)
mp[i].clear();
for (int i = 0;i <= Dep;i ++) a[i].clear();
Dep = 0;
for (auto E : edge) {
int u = E.first, v = E.second;
addEdge(u,v); addEdge(v,u);
}
vector<int> ask;
for (int i = 1;i <= n;i ++) ask.push_back(i);
pair<int,int> res = Query(ask);
int rt = res.first, sum = res.second;
dfs(rt,0,0);
int l = sum >> 1, r = min(Dep,sum);
int X = 0;
while (l <= r) {
int mid = l + r >> 1;
res = Query(a[mid]);
if (res.second == sum) l = mid + 1, X = res.first;
else r = mid - 1;
}
for (int i = 0;i <= Dep;i ++) a[i].clear();
Dep = 0; dfs(X,0,0);
return make_pair(X, Query(a[sum]).first);
}
int T,n; char AC_or_WA[maxn];
vector<pair<int,int> > Edge;
int main() {
scanf("%d",&T);
while (T --) {
scanf("%d",&n);
Edge.clear();
for (int i = 1,u,v;i < n;i ++) {
scanf("%d%d",&u,&v);
Edge.emplace_back(u,v);
}
pair<int,int> ans = Solve(n,Edge);
printf("! %d %d\n",ans.first,ans.second);
fflush(stdout);
scanf("%s",AC_or_WA + 1);
}
return 0;
}