蒟蒻的第一道正式交互题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 eq 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
",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);
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
",ans[n]);
fflush(stdout);
}
int main()
{
srand(time(0));
// prework();
init();
work();
return 0;
}