题目大意
3 n 2 + 30 \cfrac{3n}{2}+30 23n+30次询问,求解出这个隐藏的序列(permutation)。
比赛反思
自己迟迟没有想出来,还是xun巨告诉自己的。原因总结一下大概有以下几点
- 交互题做的太少,没有信心,每个输出后面都要加一个
fflush(stdout)
,并且原来CF如果wa1看输出 - 没有注意数据范围,CF中很多题目,会有多解,类似思维题,构造题,这个时候就可以利用数据范围,化繁为简。
当然,有的题目不通过数据范围必然做不出来
- 学会静下心来,沉稳思路,寻找突破口,很明显题目都说了是一个permutation了,那么不可能是无头苍蝇一直乱找,而且已经有max和min了,理应想到对于一个数,其余数与他匹配都可以得到其余数字本身,显然这个数应该是最大或最小
算法思路
分为两步来做
1.先找到1,但是直接n次找1,后面会不够用,所以两个数两个数的找,即花
n
2
+
2
\cfrac{n}{2}+2
2n+2次去找(后面解释为啥是这个数),构造
m
i
n
(
m
a
x
(
1
,
p
i
)
,
m
a
x
(
2
,
p
j
)
)
min(max(1,p_i),max(2,p_j))
min(max(1,pi),max(2,pj))如果询问输出的结果是1,那么
i
i
i就是我们要找的位置无疑了。但是如果输出为2,则只有两种情况,一种是
p
i
=
=
2
,
p
j
=
=
1
p_i==2,p_j==1
pi==2,pj==1,这个时候反过来判断一下,因为另一种
p
i
>
2
,
p
j
=
=
2
p_i>2,p_j==2
pi>2,pj==2,当然这不是我们需要的情况。再加上这两种情况的询问,总共需要
n
2
+
2
\cfrac{n}{2}+2
2n+2次。
2.
m
a
x
(
m
i
n
(
n
−
1
,
1
)
,
m
i
n
(
n
,
p
j
)
)
max(min(n-1,1),min(n,p_j))
max(min(n−1,1),min(n,pj))<==>等价于
m
a
x
(
1
,
p
j
)
max(1,p_j)
max(1,pj),最后得出的就是
p
j
p_j
pj,这样我们就可以
n
−
1
n-1
n−1次询问得到整个数组
代码实现
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
#define el '\n'
#define cl putchar('\n')
const int N=3e4+10,M=1e3+10;
int T,n,m,x,y,k,mi;
int a[N];
int main() {
cin.tie(0);
cout.tie(0);
cin>>T;
int inf=2e9;
// mi=inf;
while(T--) {
cin>>n;
mi=inf;
/*
这场比赛自己最大的问题,就是没有注意数据范围
有时候数据范围也是CF中一个很重要的突破口
构造题
先查询到数组中1所在的位置,两个两个数查询,
min(max(1,pi),max(2,pj))
*/
if(n%2==0){//n为偶数
for(int i=1;i<n;i+=2){
printf("? %d %d %d %d\n",2,i,i+1,1);
fflush(stdout);
cin>>x;
if(x==1){
mi=i;
a[i]=1;
break;
}
if(x==2){
//出现2的情况有两种
//一种是pi==2,pj==1,这个时候反过来判断一下
//另一种pi>2,pj==2
//所以这整个循环最多会被执行N/2+2次
printf("? %d %d %d %d\n",2,i+1,i,1);
fflush(stdout);
cin>>y;
if(y==1){
mi=i+1;
a[i+1]=1;
break;
}
}
}
}
else {//n为奇数
for(int i=1;i<n-1;i+=2){
printf("? %d %d %d %d\n",2,i,i+1,1);
fflush(stdout);
cin>>x;
if(x==1){
mi=i;
a[i]=1;
break;
}
if(x==2){//出现2的情况可能是pj==1,这个时候反过来判断一下
printf("? %d %d %d %d\n",2,i+1,i,1);
fflush(stdout);
cin>>y;
if(y==1){
mi=i+1;
a[i+1]=1;
break;
}
}
}
if(mi==inf){//前面N-1个都没有1,那么必然在第n个数了
mi=n;
a[mi]=1;
}
}
//max(min(n-1,1),min(n,pj))
for(int i=1;i<=n;i++){//n-1次得到整个序列
if(i==mi)continue;
printf("? %d %d %d %d\n",1,mi,i,n-1);
//任何数都比1大,有时候可以假设知道某些条件,然后看看能否推出答案,一步步逆推出答案
fflush(stdout);
cin>>a[i];
}
printf("!");
for(int i=1;i<=n;i++)printf(" %d",a[i]);
printf("\n");
fflush(stdout); //交互题每次清空完都要记得清空缓存区
}
}