洛谷P7843

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\) 是不是在一块就可以了。

但是我是个懒逼,所以我把上面所有的代码全拼起来了。

然后我就过了。

上一篇:在Safari里也能像Chrome里一样,通过执行js修改变量的值,在debugger里立即生效


下一篇:Android多线程断点续传下载原理及实现,2021阿里Android笔试总结