【JOISC2018|2019】【20190622】minerals

博客围绕一道交互题展开,题目有\(2n\)个物品,存在唯一两两配对关系,有一个显示物品种类数\(C\)的盒子,每次可拿出或放入物品。题解采用分治策略,先将物品分成两个集合,不断分治下去,复杂度约为\(O(3.5N + 2NlogN)\),还通过求导确定分治两部分之比。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

交互题

\(2n\)个物品,编号为\(1-2n\),存在唯一的两两配对关系,即有\(n\)种物品

有一个盒子,初始为空,盒子上会显示里面存在的物品种类数\(C\)

你每次操作可以将一个物品从盒子里拿出或者放入盒子

$n \le 43000 $,次数限制\(10^6\)

题解

  • 首先依次加入所有物品,考虑C变和不变可以将物品分成两个对应的集合AB

  • 在盒子里保留A的一半,依次改变B的状态,考虑C变和不变可以将B继续分成对应的两个集合

  • 交换AB,一直分治下去,复杂度大约是\(O(3.5N+2NlogN)\)

  • 然而这题最优秀的一点在于后面(图片来自官解):

    我们假设分治部分的次数复杂度为\(f(N) = tN \ logN\) ,设每次分治的两部分之比为p : 1 - p

    img

    (求导)求极值点:

    img

  • 那 p 就一反套路地取0.382好了

    #include "minerals.h"
    #include<bits/stdc++.h>
    #define K 0.38
    
    using namespace std;
    
    const int maxN=43010;
    int n,P[maxN],Q[maxN],vis[maxN<<1];
    int pl[maxN],pr[maxN],ql[maxN],qr[maxN];
    
    bool query(int x){
      static int now,lst,re;
      vis[x]^=1;now=Query(x);
      re=(now!=lst);lst=now;
      return re;
    }
    
    void Swap(int l1,int r1){
      static int tmp[maxN];
      for(int i=1;i<=l1;++i)tmp[i]=pl[i];
      for(int i=1;i<=r1;++i)pl[i]=pr[i];
      for(int i=1;i<=l1;++i)pr[i]=tmp[i];
    }//直接swap两个指针的话似乎会把指向的数组全部交换
    
    void solve(int*p,int*q,int len){
      if(len==1){
          Answer(p[1],q[1]);
          return;
      }
      int l1,r1,l2,r2;
      l1=r1=l2=r2=0;
      for(int i=1;i<=len;++i){
          if(vis[p[i]])pl[++l1]=p[i];
          else pr[++r1]=p[i];
      }
      if(l1>r1)Swap(l1,r1),swap(l1,r1);
      int base=max(1,(int)(K*len));
      while(l1<base)query(pr[r1]),pl[++l1]=pr[r1--];
      while(l1>base)query(pl[l1]),pr[++r1]=pl[l1--];
      if(vis[pl[1]])Swap(l1,r1),swap(l1,r1);
      for(int i=1;i<=len;++i)if(query(q[i])){
          ql[++l2]=q[i];
          if(l2==l1){for(++i;i<=len;++i)qr[++r2]=q[i];}
      } else{
          qr[++r2]=q[i];
          if(r2==r1){for(++i;i<=len;++i)ql[++l2]=q[i];}
      }
      for(int i=1;i<=l1;++i)p[i]=pl[i];
      for(int i=1;i<=l2;++i)q[i]=ql[i];
      for(int i=1;i<=r1;++i)p[i+l1]=pr[i];
      for(int i=1;i<=r2;++i)q[i+l2]=qr[i];    
      solve(q,p,l1);
      solve(q+l1,p+l1,r1);
    }
    
    void Solve(int N) {
      n=N;
      int cnt1=0,cnt2=0;
      for(int i=1;i<=2*n;++i){
          if(query(i))P[++cnt1]=i;
          else Q[++cnt2]=i;
      }   
      solve(P,Q,n);
    }
    //一道非常有意思的交互题
    //20190622

转载于:https://www.cnblogs.com/Paul-Guderian/p/11073800.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值