Solution
初始化 ,其余 ,? 1 2 加入询问队列。
每次从队头取出询问 ? u v (保证 ,),返回的结果是 , 路径的中点 。
- 若 ,则将
? u p加入队列。 - 若 ,则将
? p v加入队列。 - 特殊地,若 ,则记录 , 有边连接,不加入新的询问到队列。
如果队列为空,且确定的边数量不足 ,则遍历找出一个 的点 ,并把 ? 1 i 加入队列。
其实本质就是对 不同的两个点 、,二分找出两点路径上最靠近 的点 且 ,并更改 ,让 与路径上的前一个点建边。
因此最坏情况下,每 次询问就会建一条边,总询问次数大约在 级别,满足题目要求。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int n, vis[N], ans_cnt, T, res;
pair<int, int> ans[N];
queue<pair<int, int> > q;
void solve()
{
ans_cnt = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
vis[i] = 0;
while (q.size()) q.pop();
q.push({1, 2});
vis[1] = 1;
while (q.size() && ans_cnt < n - 1) {
int u = q.front().first, v = q.front().second;
q.pop();
printf("? %d %d\n", u, v);
fflush(stdout);
scanf("%d", &res);
if (res == -1) exit(0);
if (res == u) {
vis[v] = 1;
ans[++ans_cnt] = {u, v};
} else {
if (vis[res])
q.push({res, v});
else
q.push({u, res});
}
if (q.empty()) {
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
q.push({1, i});
break;
}
}
}
}
printf("! ");
fflush(stdout);
for (int i = 1; i <= ans_cnt; i++) {
printf("%d %d ", ans[i].first, ans[i].second);
fflush(stdout);
}
putchar('\n');
fflush(stdout);
}
int main()
{
scanf("%d", &T);
while (T--) solve();
return 0;
}