「Codeforces 1438F」Olha and Igor

8 篇文章 0 订阅
6 篇文章 0 订阅

题目大意

这是一道 交互题

有一个层数为 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 3h18

题解

发现询问 ( 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值