传送门
题意:有一个隐藏的排列p,每次可以询问ask(t,i,j,x)
。反馈规则如下:
在不超过⌊(3⋅n)/2⌋+30次询问下,输出这个排列。
类似这种通过询问求出序列的交互题,一个很重要的突破点时现找到一个确定的数,然后通过确定的数逐个求出其他数。
分析:
- 首先我们观察t == 1的询问,我们可以令
x=n-1
,如果回答是n , 那么说明pj就是n 。所以我们可以通过枚举,在n次询问下求出n的位置。(当然后面会提到这里可以优化到n/2次) - 知道了n的位置就好办了,我们可以通过t == 2的查询,令
x=1,pj=n
,那么回答的就是pi 。总共n次询问将排列每个数求出 。 - 优化:可以看到上述过程查询是2n次的,我们可以将求n位置的过程优化到n/2次查询。
- 具体做法:当我们用t == 1的查询,
x=n-1
,查i j这两个位置时,如果回答是n,那好办,pj就是n ,但是如果n在i的位置,回答就是n-1了 ,所以如果回答是n-1,我们还需要再反过来查一下j i 。 所以枚举n/2次就可以解决。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 1e4+10;
const int mx = 40;
const int mod = 1e9+7;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int INF = 1e9+7;
int p[maxn];
int n;
inline int ask(int t,int i,int j,int x)
{
printf("? %d %d %d %d\n",t,i,j,x);
fflush(stdout);
int reply;
scanf("%d",&reply);
//如果是n 那么j位置直接就是n了
if(reply == n)
{
p[j]=n;
return j;
}
//如果是n-1 那么i位置有可能是n 还需要判断
else if(reply == n-1)
{
printf("? %d %d %d %d\n",t,j,i,x);
fflush(stdout);
scanf("%d",&reply);
if(reply == n)
{
p[i]=n;
return i;
}
}
return 0;
}
int main()
{
int t;scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
if(n==1)
{
printf("! 1\n");
fflush(stdout);
continue;
}
memset(p,-1,sizeof p);
int f=0;
for(int i=1;i+1<=n && !f;i+=2) //进循环表示1不是n
{
f=ask(1,i,i+1,n-1);
}
if(!f) //当且仅当n是奇数且n在排列最后一位才会如此
{
f=n;
p[n]=n;
}
//f是n的位置 找到了n直接用2去问 ask(2,i,f,1) min(max(1,pi) , max(2,n)) 得到的就是pi的值
for(int i=1;i<=n;i++)
{
if(i==f) continue;
printf("? %d %d %d %d\n",2,i,f,1);
fflush(stdout);
scanf("%d",&p[i]);
}
printf("!");
fflush(stdout);
for(int i=1;i<=n;i++) printf(" %d",p[i]);
fflush(stdout);
puts("");
fflush(stdout);
}
return 0;
}