【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;
}
posted @ 2021-12-13 11:16  stoorz  阅读(32)  评论(0编辑  收藏  举报