【CF1061F】Lost Root
题目
题目链接:https://codeforces.com/problemset/problem/1061/F
有一棵节点编号未知的满 \(k\) 叉树,其上有 \(n\) 个节点,你每次可以询问一个点是否在另两个点的路径上。需要在 \(60\times n\) 次询问操作中找到这棵树的根。
\(n\leq 1500\),\(2\leq k<n\)。
思路
首先我们可以在 \(n\) 次询问内确定一个点 \(x\) 是不是叶子节点。只需要找另一个点 \(y\),然后对于所有点 \(z(z\neq x,z\neq y)\),如果均满足 ? y z x
回答是 NO
,那么 \(x\) 就是叶子节点。
然后由于这棵树是一个满 \(k(k>1)\) 叉树,所以它至少有 \(\frac{n}{2}\) 个叶子节点。也就是说如果不断随机点并判断是不是叶子,期望 \(2\) 次就可以找到一个叶子。
然后再找到与 \(x\) 不在根节点同一子树内的另一个叶子节点 \(y\),依然随机,然后只需要 \(n\) 次询问判断 \(x\) 到 \(y\) 的路径上节点数量是不是恰好为 \(2h-1\) 即可。其中 \(h\) 是树高,可以算出来。
最后只需要找到 \(x\) 和 \(y\) 之间的所有节点并按顺序排列,最后第 \(h\) 个就是根。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1510;
int n,m,h,sum,mul,a[N];
char s[10];
bool check(int x)
{
int y=(x==1)?2:1;
for (int i=1;i<=n;i++)
if (i!=x && i!=y)
{
printf("? %d %d %d\n",y,x,i);
fflush(stdout);
scanf("%s",s);
if (s[0]=='Y') return 0;
}
return 1;
}
bool check2(int x)
{
int cnt=2;
for (int i=1;i<=n;i++)
if (i!=x && i!=a[1])
{
printf("? %d %d %d\n",x,i,a[1]);
fflush(stdout);
scanf("%s",s);
if (s[0]=='Y') cnt++;
}
if (cnt==2*h-1) return 1;
return 0;
}
int main()
{
srand(time(0));
scanf("%d%d",&n,&m);
mul=1;
for (int i=1;i<=n;i++)
{
sum+=mul; mul*=m;
if (sum==n) { h=i; break; }
}
a[1]=rand()%n+1;
while (!check(a[1])) a[1]=rand()%n+1;
a[2]=rand()%n+1;
while (a[2]==a[1] || !check(a[2]) || !check2(a[2])) a[2]=rand()%n+1;
m=2;
for (int i=1;i<=n;i++)
if (i!=a[1] && i!=a[2])
for (int j=1;j<m;j++)
{
printf("? %d %d %d\n",a[j],i,a[j+1]);
fflush(stdout);
scanf("%s",s);
if (s[0]=='Y')
{
for (int k=m;k>j;k--) a[k+1]=a[k];
a[j+1]=i; m++; break;
}
}
printf("! %d\n",a[(m+1)/2]);
return 0;
}