CF2277E Tree Guessing

成功入选榜单前 10 10 10 qwq!
思路
为了方便表述,我们定义节点 i i i 的权值为 p i p_i pi,路径的权值为路径上所有节点的权值的最大值。
我们可以钦定树的根为 1 1 1,然后每次询问选择的顶点 x x x 也是 1 1 1,这样一来,每次询问得到的就是每个点到根节点的路径的权值之和。
考虑令根节点的权值为 n − 1 n-1 n−1,同时枚举 2 2 2 到 n n n 的节点 x x x,令 x x x 的权值为 n n n。这样一来,每次询问除经过节点 x x x 的路径外,其余路径的权值均为 n − 1 n-1 n−1,而经过节点 x x x 的路径数量为 x x x 的子树大小。于是我们可以通过 n − 1 n-1 n−1 次询问求出每个节点的子树大小。
考虑求出来子树大小有啥用?我们可以按照子树大小从大到小处理节点,求出每个节点的父亲。这样一来,处理到节点 x x x 时 x x x 的祖先一定全部处理好了。我们将节点 x x x 的权值赋值为 n n n,将所有已经处理好的节点(假设有 s i si si 个)赋值为 n − s i , n − 1 n-si,n-1 n−si,n−1,并且要求儿子节点的权值大于父亲节点的权值,其余的点随便赋。这样,假设不考虑节点 x x x 的时权值为 s u m 1 sum1 sum1,我们询问交互库给出的答案为 s u m 2 sum2 sum2, s u m 2 − s u m 1 sum2-sum1 sum2−sum1 形成的差值就是 s x × ( n − p f a x ) s_x \times(n-p_{fa_x}) sx×(n−pfax),可以求出 f a x fa_x fax。
恰好 2 n − 2 2 n-2 2n−2 次询问。
代码
时间复杂度 O ( n 2 ) O(n^2) O(n2)。
cpp
#include<bits/stdc++.h>
using namespace std;
int t,n,p[505],si[505],fa[505];
long long qiu()
{
cout<<"? 1 ";
for(int i=1;i<=n;i++) cout<<p[i]<<" ";
cout<<endl;
long long x;
cin>>x;
return x;
}
pair<int,int> q[505];
int main()
{
cin>>t;
while(t--)
{
cin>>n;
p[1]=n-1;
for(int i=2;i<=n;i++)
{
p[i]=n;
int cnt=0;
for(int j=2;j<=n;j++)
if(j!=i) cnt++,p[j]=cnt;
si[i]=qiu()-(n-1)*1ll*n;
}
si[1]=n;
for(int i=1;i<=n;i++) fa[i]=0;
for(int i=1;i<=n;i++) q[i]={si[i],i};
sort(q+1,q+1+n);
for(int i=n-1;i>=1;i--)
{
int x=q[i].second;
p[x]=n;
for(int j=1;j<i;j++) p[q[j].second]=j;
for(int j=n;j>i;j--) p[q[j].second]=n-1-(j-i-1);
long long sum2=qiu(),sum1=0;
for(int j=n;j>i;j--) sum1+=si[q[j].second]*1ll*p[q[j].second]-si[q[j].second]*1ll*p[fa[q[j].second]];
fa[x]=q[i+(sum2-sum1)/si[x]].second;
}
cout<<"!"<<endl;
for(int i=2;i<=n;i++) cout<<i<<" "<<fa[i]<<endl;
}
return 0;
}