zoukankan      html  css  js  c++  java
  • 洛谷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
    ");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
    ",a,c);fflush(stdout);ans1=read();
      printf("! %d %d
    ",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
    ",ed[1],ed[2],ed[3],ed[4]);
        fflush(stdout);return 0;
      }
      printf("! %d %d
    ",b,c);fflush(stdout);ans3=read();
      printf("! %d %d
    ",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
    ",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
    ",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 imes 10^4)

    这启发我们可以用类似倍增的做法来搞。

    可以发现,((lceilfrac{n}{2} ceil,n]) 中的每个数对 (lceilfrac{n}{2} ceil) 取模都可以得到不同的结果。

    所以,如果我们知道了 ([1,lceilfrac{n}{2} ceil]) 中每个数的位置,那么剩下的数位置可以通过不断进行操作一来确定。

    想要确定 ([1,lceilfrac{n}{2} ceil]),可以转化为知道了 ([1,lceilfrac{n}{4} ceil]) 来用同样的方法求 ((lceilfrac{n}{4} ceil,lceilfrac{n}{2} ceil]) 得到。

    所以我们可以通过递归分治的方式来处理。

    我们在数轴上找出每个长为 (n imes 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
    ",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
    ",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 imes 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
    ",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
    ",a,c);fflush(stdout);ans1=read();
    printf("! %d %d
    ",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
    ",b,c);fflush(stdout);ans3=read();
      printf("! %d %d
    ",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
    ",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 imes frac{1}{2}^i) 大小分的块可能使得 (3,4) 不在同一块中而 (4,5) 在同一块中,所以找完 (1)(4) 的位置后再处理后面时 (5) 不会被处理到。

    这里特判一下 (3,4) 是不是在一块就可以了。

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

    然后我就过了。

  • 相关阅读:
    【NOIP 2003】 加分二叉树
    【POJ 1655】 Balancing Act
    【HDU 3613】Best Reward
    【POJ 3461】 Oulipo
    【POJ 2752】 Seek the Name, Seek the Fame
    【POJ 1961】 Period
    【POJ 2406】 Power Strings
    BZOJ3028 食物(生成函数)
    BZOJ5372 PKUSC2018神仙的游戏(NTT)
    BZOJ4836 二元运算(分治FFT)
  • 原文地址:https://www.cnblogs.com/KnightL/p/15168817.html
Copyright © 2011-2022 走看看