题目大意
这是一道 交互题 。
有一个层数为 h h h 、节点数为 n n n 的 满二叉树 ,你可以进行以下询问至多 n + 420 n+420 n+420 次:
选择一个三元组 ( u , v , w ) (u,v,w) (u,v,w) ,要求这三个数互不相同,交互库会返回以 w w w 为根时 u u u 和 v v v 的 最近公共祖先 。
你要输出根节点的编号。
满足 3 ≤ h ≤ 18 3\le h\le 18 3≤h≤18 。
题解
发现询问 ( u , v , w ) (u,v,w) (u,v,w) 得到的 l c a lca lca 其实就是树上离 u , v , w u,v,w u,v,w 的距离之和最小的那个点。
考虑点 k k k 是多少个三元组的答案。假设以 k k k 为根时,其3个儿子的子树大小分别为 s 1 , s 2 , s 3 s1,s2,s3 s1,s2,s3 (如果某个儿子不存在,对应的 s s s 就为 0 0 0 ),那么它就是 s 1 s 2 s 3 + s 1 s 2 + s 2 s 3 + s 1 s 3 s_1 s_2 s_3+s_1 s_2+s_2 s_3+s_1 s_3 s1s2s3+s1s2+s2s3+s1s3 个三元组的答案。
上面式子中每一项的含义分别是 u , v , w u,v,w u,v,w 分别在三个子树中、 u , v , w u,v,w u,v,w 中有一个在 k k k 上,其它两个分散在两个 不同的 子树中。
显然根节点的两个儿子是对应最多三元组的。而估算一下,这两个点占了所有三元组的 20 % 20\% 20%左右。
因此可以先随机询问 420 420 420 次,出现次数最多的两个答案即为根节点的两个儿子。然后 O ( n ) O(n) O(n) 找根节点就好了。
代码
#include<ctime>
#include<cstdio>
#include<random>
using namespace std;
#define fo(i,l,r) for(i=l;i<=r;++i)
#define N 262145
int n,cnt[N];
int main()
{
int h,s1=0,s2=0,u,v,w,lca,i;
scanf("%d",&h),n=(1<<h)-1;
uniform_int_distribution<unsigned int> num(0,n-1);
default_random_engine e((unsigned)time(0));
fo(i,1,422)
{
u=num(e),v=num(e);
while(u==v) v=num(e);
w=num(e);
while(u==w||v==w) w=num(e);
printf("? %d %d %d\n",u+1,v+1,w+1),
fflush(stdout),scanf("%d",&lca),
++cnt[lca];
}
fo(i,1,n)
if(cnt[i]>cnt[s1]) s2=s1,s1=i;
else if(cnt[i]>cnt[s2]) s2=i;
fo(i,1,n) if(i!=s1&&i!=s2)
{
printf("? %d %d %d\n",s1,s2,i),
fflush(stdout),scanf("%d",&lca);
if(i==lca) return printf("! %d\n",i),0;
}
printf("%d\n",rand()&1?s1:s2);
return 0;
}