CF1364E X-OR

蒟蒻的第一道正式交互题QWQ。

首先我们要明确一点:如果知道了排列中 \(0\) 的位置,所有其他位置均可以通过一次询问求出。所以我们的思路是首先找到 \(0\) 的位置。

考虑一个暴力的判断一个位置 \(x\) 是否是 \(0\) 的方法:

  1. 随机出 30个值域在\([0,n-1]\)范围内的数,记作 \(a\) 序列
  2. \(res=2047\),然后依次 \(res\&=Query(a[i],x)\)
  3. 若任何时刻 \(res=0\),则立刻判断 \(x\) 位置为 \(0\),若最后 \(res\) 都不为 \(0\),则可以大致判断 \(x\) 位置不为 \(0\)

(其中 \(Query\) 为对交互库的询问,上面算法在很大的概率上是正确的)

但显然我们不能对每个位置依次判断其是否为 \(0\)

但我们可以很快的排除不是 \(0\) 的位置。

给你三个位置 \(a,b,c\)\(Query(a,b)\) 的答案 \(now\)

  1. \(Query(a,c)<now\),则 \(b\) 位置处不为 \(0\)
  2. \(Query(a,c)>now\),则 \(c\) 位置处不为 \(0\)
  3. \(Query(a,c)=now\),则 \(a\) 位置处不为 \(0\)

其中对1.2.的解释是 \(0\) 位置和任何位置的 \(Query\) 都为 \(0\),是不可能大于一个其他的 \(Query\) 的,对3.的解释是因为你要猜的数列是一个排列,所以 \(b\neq c\),所以显然 \(a\) 也不能为 \(0\)

上述流程可以对任意三个位置通过一次询问排除一个位置是 \(0\) 的可能性,这也就告诉我们,在进行 \(n-2\) 次询问后,只有 \(2\) 个位置可能为 \(0\),这时我们在使用最上面的暴力判断方法,就可以在很少次数内找到 \(0\) 的位置,这道题就做完了。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<ctime>

using namespace std;

const int N=3000;
int n,p[N],ans[N];

int Get(int x,int y)
{
	printf("? %d %d\n",x,y);
	fflush(stdout);
	int k;
	scanf("%d",&k);
	return k;
//	return p[x]|p[y];
}

void init()
{
	scanf("%d",&n);
}

bool check(int x)
{
	int A=2047;
	for (int _=1;_<=30;_++)
	{
		int tmp=rand()%n+1;
		while(tmp==x) tmp=rand()%n+1;
		A&=Get(x,tmp);
		if(A==0) return 1;
	}
	return 0;
}

void prework()
{
	n=rand()%10+1;
	while(n<3) n=rand()%10+1;
	for (int i=1;i<=n;i++)
		p[i]=i-1;
	random_shuffle(p+1,p+1+n);
	printf("%d\n",n);
	for (int i=1;i<=n;i++)
		printf("%d ",p[i]);puts("");
}

void work()
{
	int A=1,B=2,now=Get(A,B);
	for (int i=3;i<=n;i++)
	{
		int C=Get(A,i);
		if(C<now) B=i,now=C;
		else if(C==now) A=i,now=Get(A,B);
	}
	int pos=check(A)?A:B;
	for (int i=1;i<=n;i++)
		if(i!=pos)
			ans[i]=Get(pos,i);
	printf("! ");
	for (int i=1;i<n;i++)
		printf("%d ",ans[i]);
	printf("%d\n",ans[n]);
	fflush(stdout);
}

int main()
{
	srand(time(0));
//	prework();
	init();
	work();
	return 0;
}
posted @ 2020-06-25 23:47  With_penguin  阅读(153)  评论(0编辑  收藏  举报