题面传送门
很妙的一道题。
根号算法在这上面只有\(48\)分别想了。
因为随机化算法没有前途所以考虑随机。
容易发现如果我们知道了一个子树的节点,又知道它下一层点的子树,那么我们可以有一个树不询问而直接算出来。
这个子树随机即可,因为树剖的性质所以这个可以保证\(O(nlogn)\)
code:
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<algorithm>
#include<bitset>
#include<ctime>
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define l(x) x<<1
#define r(x) x<<1|1
#define re register
#define ll long long
#define S 10000000
#define N 5000
#define R(x) (rand()*rand()%x)
using namespace std;
int n,y,fa[N+5],d[N+5],b[N+5],head,now,f[N+5];
vector<int> g[N+5];
I void dfs(int x){
if(g[x].size()==1) return;for(int i=head=0;i<g[x].size();i++) now=g[x][i],d[now]==d[x]+1&&(b[++head]=now,fa[now]=x),f[now]++;f[x]--;
int pus=R(g[x].size()),t,son;while(g[x][pus]==x) pus=R(g[x].size());pus=g[x][pus];
for(int i=1;i<=head;i++){
if(b[i]==pus) {son=b[i];break;}printf("? 1 %d %d\n",b[i],pus);fflush(stdout);scanf("%d",&t);
if(t<d[pus]-d[x]){son=b[i];break;};
}
for(int i=1;i<=head;i++){
if(b[i]==son) continue;printf("? 2 %d\n",b[i]);fflush(stdout);scanf("%d",&t);
while(t--) scanf("%d",&y),g[b[i]].push_back(y),f[y]--;
}
for(int i=1;i<=n;i++) f[i]&&(g[son].push_back(i),f[i]=0);for(int i=0;i<g[x].size();i++) now=g[x][i],d[now]==d[x]+1&&(dfs(now),0);
}
int main(){
re int i,j,h;srand(time(0));scanf("%d",&n);d[1]=1;g[1].push_back(1);
for(i=2;i<=n;i++) printf("? 1 1 %d\n",i),fflush(stdout),scanf("%d",&d[i]),d[i]++,g[1].push_back(i);dfs(1);
printf("! ");for(i=2;i<=n;i++) printf("%d ",fa[i]);fflush(stdout);
}