Subtask 1
注意到只有四个数。
可以先通过一次操作二找出 \(3,4\) 的位置和 \(1,2\) 的位置。
然后再分别用四次操作一,让 \(3,4\) 来对 \(1,2\) 取模确定四个位置。
n=read();m1=read();m2=read();m3=read();
if(n==4){
printf("? 4 1 2 3 4 3\n");fflush(stdout);
k=read();a=read();b=read();
for(int i=1;i<=4;i++)if(i!=a&&i!=b){if(!c)c=i;else d=i;}
printf("! %d %d\n",a,c);fflush(stdout);ans1=read();
printf("! %d %d\n",a,d);fflush(stdout);ans2=read();
if(ans1+ans2==0) ed[b]=3,ed[a]=4;
else{
ed[b]=4,ed[a]=3;
if(ans1) ed[c]=2,ed[d]=1;else ed[d]=2,ed[c]=1;
printf("A %d %d %d %d\n",ed[1],ed[2],ed[3],ed[4]);
fflush(stdout);return 0;
}
printf("! %d %d\n",b,c);fflush(stdout);ans3=read();
printf("! %d %d\n",b,d);fflush(stdout);ans4=read();
if(ans3) ed[c]=2,ed[d]=1;else ed[c]=1,ed[d]=2;
printf("A %d %d %d %d\n",ed[1],ed[2],ed[3],ed[4]);
fflush(stdout);return 0;
}
Subtask 2
发现 \(m_2=n\),且 \(m_3\) 是 \(m_2^2\)。
那么我们可以通过 \(n\) 次询问来确定每个数的位置。
每次询问 \(n\) 个数,询问的上界减一,然后找出给的数中没有被确定位置的数即可。
n=read();m1=read();m2=read();m3=read();
if(n==500){
for(int i=n;i>=1;i--){
printf("? %d ",i);
for(int j=1;j<=n;j++)
if(!flag[j]) printf("%d ",j);
printf("%d\n",i);fflush(stdout);
int x=read(),y=read();ed[y]=i,flag[y]=1;
}
printf("A ");
for(int i=1;i<=n;i++) printf("%d ",ed[i]);
fflush(stdout);return 0;
}
Subtask 3~6
没看出来性质 A 可以怎么搞。
看最下面的数据范围,发现 \(m_1\) 很大,\(m_2\) 很小,但是 \(2^{17}>5\times 10^4\)。
这启发我们可以用类似倍增的做法来搞。
可以发现,\((\lceil\frac{n}{2}\rceil,n]\) 中的每个数对 \(\lceil\frac{n}{2}\rceil\) 取模都可以得到不同的结果。
所以,如果我们知道了 \([1,\lceil\frac{n}{2}\rceil]\) 中每个数的位置,那么剩下的数位置可以通过不断进行操作一来确定。
想要确定 \([1,\lceil\frac{n}{2}\rceil]\),可以转化为知道了 \([1,\lceil\frac{n}{4}\rceil]\) 来用同样的方法求 \((\lceil\frac{n}{4}\rceil,\lceil\frac{n}{2}\rceil]\) 得到。
所以我们可以通过递归分治的方式来处理。
我们在数轴上找出每个长为 \(n\times \frac{1}{2}^{i}\) 的段,然后处理这一段内的数的位置即可。
void work(int l,int r,int cnt){
int mid=(r+1)/2+1;all=0;
if(l==r){for(int i=1;i<=n;i++)if(!pos[i]) pos[i]=cnt;return;}
for(int i=1;i<=n;i++)if(!pos[i]) que[++all]=i;
printf("? %d ",all);for(int i=1;i<=all;i++) printf("%d ",que[i]);
printf("%d\n",mid);fflush(stdout);int x=read();
for(int i=1;i<=x;i++) pos[read()]=cnt;work(l,mid-1,cnt+1);
}
//下面是主函数内的部分
n=read();m1=read();m2=read();m3=read();
if(m2>=17){
for(int i=1;i<=n;i++) num[i]=i;
work(1,n,1);sort(num+1,num+n+1,cmp);
ed[num[1]]=1;ed[num[2]]=2;int maxx,id=num[2];
for(int i=3;i<=n;i++){
if(pos[num[i]]!=pos[num[i-1]]) lst=id,maxx=id=0;
printf("! %d %d\n",num[i],lst);fflush(stdout);int x;
ed[num[i]]=ed[lst]+(x=read());if(!x)ed[num[i]]=(ed[lst]<<1);
if(maxx<ed[num[i]])maxx=ed[num[i]],id=num[i];
}
printf("A");for(int i=1;i<=n;i++) printf(" %d",ed[i]);
fflush(stdout);return 0;
}
Subtask 7
可以发现对于最后一组数据,\(2^{15}\le 5\times 10^4<2^{16}\)。也就是说我们操作二理论上多进行了一次。
想办法怎么减少次数。
我们发现递归的时候是最后剩余了一个 \(1\) 然后退出的。此时我们可以找出 \(1,2,3,4\) 的位置。
如果我们不用操作二找出最后一个 \(1\) 的位置,而是采用 Subtask 1 的方法来的话,理论上就可以使操作二控制在一个可行的范围内了。
然后我们在 Subtask 3~6 的代码上套一个 Subtask 1 的代码就可以通过这个测试点。
void work2(int l,int r,int cnt){
int mid=(r+1)/2+1;all=0;
if(mid==2){for(int i=1;i<=n;i++)if(!pos[i]) pos[i]=cnt;return;}
for(int i=1;i<=n;i++)if(!pos[i]) que[++all]=i;
printf("? %d ",all);for(int i=1;i<=all;i++) printf("%d ",que[i]);
printf("%d\n",mid);fflush(stdout);int x=read();
for(int i=1;i<=x;i++) pos[read()]=cnt;work2(l,mid-1,cnt+1);
}
//下面是主函数内的部分
for(int i=1;i<=n;i++) num[i]=i;
work2(1,n,1);sort(num+1,num+n+1,cmp);
int c=num[2],d=num[1],a=num[3],b=num[4];
printf("! %d %d\n",a,c);fflush(stdout);ans1=read();
printf("! %d %d\n",a,d);fflush(stdout);ans2=read();
if(ans1+ans2!=0){
ed[a]=3;ed[b]=4;
if(ans1) ed[c]=2,ed[d]=1;else ed[d]=2,ed[c]=1;
}
else{
ed[a]=4;ed[b]=3;
printf("! %d %d\n",b,c);fflush(stdout);ans3=read();
printf("! %d %d\n",b,d);fflush(stdout);ans4=read();
if(ans3) ed[c]=2,ed[d]=1;else ed[c]=1,ed[d]=2;
}
int maxx,id;
if(ed[num[3]]==4) id=num[3];
else id=num[4];
for(int i=5;i<=n;i++){
if(pos[num[i]]!=pos[num[i-1]]) lst=id,maxx=id=0;
printf("! %d %d\n",num[i],lst);fflush(stdout);int x;
ed[num[i]]=ed[lst]+(x=read());if(!x)ed[num[i]]=(ed[lst]<<1);
if(maxx<ed[num[i]])maxx=ed[num[i]],id=num[i];
}
printf("A");for(int i=1;i<=n;i++) printf(" %d",ed[i]);
fflush(stdout);return 0;
但是这份代码又不能过掉 Subtask 3~4,因为在前面的小数据递归时,按 \(n\times \frac{1}{2}^i\) 大小分的块可能使得 \(3,4\) 不在同一块中而 \(4,5\) 在同一块中,所以找完 \(1\) 到 \(4\) 的位置后再处理后面时 \(5\) 不会被处理到。
这里特判一下 \(3,4\) 是不是在一块就可以了。
但是我是个懒逼,所以我把上面所有的代码全拼起来了。
然后我就过了。