CF1364E X-OR
蒟蒻的第一道正式交互题QWQ。
首先我们要明确一点:如果知道了排列中 \(0\) 的位置,所有其他位置均可以通过一次询问求出。所以我们的思路是首先找到 \(0\) 的位置。
考虑一个暴力的判断一个位置 \(x\) 是否是 \(0\) 的方法:
- 随机出 30个值域在\([0,n-1]\)范围内的数,记作 \(a\) 序列
- 取 \(res=2047\),然后依次 \(res\&=Query(a[i],x)\)
- 若任何时刻 \(res=0\),则立刻判断 \(x\) 位置为 \(0\),若最后 \(res\) 都不为 \(0\),则可以大致判断 \(x\) 位置不为 \(0\)。
(其中 \(Query\) 为对交互库的询问,上面算法在很大的概率上是正确的)
但显然我们不能对每个位置依次判断其是否为 \(0\)。
但我们可以很快的排除不是 \(0\) 的位置。
给你三个位置 \(a,b,c\) 和 \(Query(a,b)\) 的答案 \(now\):
- 若 \(Query(a,c)<now\),则 \(b\) 位置处不为 \(0\)。
- 若 \(Query(a,c)>now\),则 \(c\) 位置处不为 \(0\)。
- 若 \(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;
}
由于博主比较菜,所以有很多东西待学习,大部分文章会持续更新,另外如果有出错或者不周之处,欢迎大家在评论中指出!