原题链接:Problem - D - Codeforces
题意:交互题,每次可以询问二个点之间的简单路径是否通过0点,如果通过返回1,否则返回0,要求输出每个节点的父亲。对于这颗树有三个特殊条件。1,如果断开0节点的全部边,那么和0节点相连的节点都是子树每个路径的终点。2,如果一个节点的编号大于另一个节点的编号,那么这个节点的父亲编号大于等于另一个节点的父亲编号。3,1号节点必须和二个点相连,其中一个必须是0节点。
思路:考虑三个特殊特殊条件,第一个可以知道,除了0节点外,其他节点的儿子只有一个。因为第一个条件,所有第二个条件的大于的等于就严格变成了大于。第三个条件可以知道,可以通过询问1和其他点来判断其他点是不是0的儿子。
//冷静,冷静,冷静
//调不出来就重构
//#pragma GCC optimize(2)
//#pragma GCC optimize("O3")
#include<bits/stdc++.h>
#define count2(x) __builtin_popcountll(x)
#define is2(x) __builtin_ffsll(x)
#define endl '\n'
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
random_device rd;
mt19937_64 mt(rd());
typedef pair<ll,ll> pii;
const int N=1e6+10,mod=1000000007,P=131;
bool ask(ll a,ll b)
{cout<<"? "<<a<<' '<<b<<endl;ll v;cin>>v;return v;
}
ll fa[N];
void Jiuyuan()
{ll n;cin>>n;ll now=2;//找爸爸的人 queue<ll> op;//找儿子的队列,从小到大 op.push(1);while(now<n)//因为0有很多儿子,所以先解决0的儿子问题 {if(ask(now,1))//如果经过0,那么就是0的儿子 {fa[now]=0;op.push(now);now++;}else//否则就是1的儿子 {fa[now]=1;op.pop();op.push(now);now++;break;}}while(now<n)//解决其他点的儿子 {if(ask(op.front(),now))//因为队列是从小到大的,那么如果当前找爸爸的点和队列的第一个节点需要经过0,那么这个点就没有儿子 {op.pop();}else//否则就是当前找爸爸的点就是队列的第一个节点的儿子 {fa[now]=op.front();op.push(now);now++;op.pop();}}cout<<"! ";for(int i=1;i<n;i++)cout<<fa[i]<<' ';cout<<endl;
}
int main()
{// ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);ll T=1;cin>>T;while(T--){Jiuyuan();}return 0;
}