【luogu CF1370F】The Hidden Pair(构造)
The Hidden Pair
题目链接:luogu CF1370F1 / luogu CF1370F2
题目大意
给你一棵树,然后你要猜两个特殊点。
每次你可以询问一个点集,会告诉你这个点集中到两个特殊点距离之和最近的点以及这个距离和。
然后要你在至多 \(11\) 次操作猜出这两个点。
\(n\leq 1000\)
思路
首先看到这个操作次数跟 \(n\) 的关系不难猜到会用 \(\log\) 次左右。
然后你不难想到一开始要选全图问一次,求出这两个特殊点之间的路径长度 \(d\)。
那第一次给的点有什么用呢?你会发现它是两个特殊点之间的路径上的一个点。
然后我们进而能发现如果一个点在这个路径上,那它到这两个点的路径长度就是这两个点之间的长度,否则就会要大于它。
然后我们考虑能不能求出其中的一个点。(因为你找到之后把距离它为 \(d\) 的点都询问一次,得到的肯定是另一个点,毕竟别的点必定不在路径上)
考虑一个神奇的方法就是二分。
你考虑把你第一次给的点当做根,然后去找那个深度较大的点。
那你会发现你如果二分一个深度,每次询问把这个深度的所有点都拿去询问,那如果得到的点的距离和是 \(d\),也就是说这个深度还有点在两个点的路径中,否则就没了。
那我们找到最大的深度的时候,这个点必定是路径其中的一个端点。
然后我们看看操作次数:
一开始一次,中间 \(\log\) 次,最后一次:\(1+10+1=12\) 刚好超了一个。
(这个时候简单版已经能过了)
那怎么优化呢?其实有个小小的性质,就是在二分的时候,你下界 \(l\) 不一定要从 \(0\) 开始。
毕竟你路径长度为 \(d\),你还要找深度大的点,那它至少的深度会在 \(\left\lceil\frac{d}{2}\right\rceil\),那把 \(l\) 一开始弄成这个,就刚好少了一次操作!
代码
#include
#include
#define clean() fflush(stdout)
using namespace std;
const int N = 1000 + 10;
int TT, n, x, y, d, deg[N], S, T;
vector G[N], ds[N];
char s[11];
void dfs(int now, int father) {
ds[deg[now]].push_back(now);
for (int i = 0; i < G[now].size(); i++) {
int x = G[now][i]; if (x == father) continue;
deg[x] = deg[now] + 1; dfs(x, now);
}
}
bool check(int x) {
if (!ds[x].size()) return 0;
printf("? %d", ds[x].size()); for (int i = 0; i < ds[x].size(); i++) printf(" %d", ds[x][i]); printf("\n"); clean();
int rx, ry;
scanf("%d %d", &rx, &ry);
if (ry == d) {S = rx; return 1;}
return 0;
}
int main() {
scanf("%d", &TT);
while (TT--) {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
scanf("%d %d", &x, &y); G[x].push_back(y); G[y].push_back(x);
}
printf("? %d", n); for (int i = 1; i <= n; i++) printf(" %d", i); printf("\n"); clean();
scanf("%d %d", &x, &d);
for (int i = 1; i <= n; i++) ds[i].clear();
deg[x] = 0; dfs(x, 0);
int l = (d + 1) / 2, r = d;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) l = mid + 1;
else r = mid - 1;
}
for (int i = 1; i <= n; i++) ds[i].clear();
deg[S] = 0; dfs(S, 0);
printf("? %d", ds[d].size()); for (int i = 0; i < ds[d].size(); i++) printf(" %d", ds[d][i]); printf("\n"); clean();
int tmp; scanf("%d %d", &T, &tmp);
printf("! %d %d\n", S, T); clean();
scanf("%s", s + 1); if (s[1] == 'I') return 0;
for (int i = 1; i <= n; i++) G[i].clear();
}
return 0;
}