2020牛客暑期多校训练营(第六场) J. Josephus Transform

  • 题意

    • 给你一个长度为 \(n\) 的排列 \(P\) (初始化为 \(P = {1,2,...,n}\)) 和 \(m\) 次操作,每次操作是一对\((k,x)\)表示对排列\(P\) 进行 \(x\)\(K- Josephus transform\)
  • 解析

    • 发现对 \(P\) 进行一次 \(K- Josephus transform\),实际上就是对 \(P\) 进行一次置换,设这个置换为 \(T\) ,进行\(x\) 次就是对 \(P\) 进行 \(x\) 次置换 \(T\) 。如果我们能得到置换 \(T\) ,那么进行 \(x\) 次就可以用快速幂 \(nlogx\) 实现。
    • 现在我们要考虑如何得到置换 \(T\) ,得到置换 \(T\) 实际上就是要知道对排列 \(P\) 进行一次 \(K- Josephus transform\) 后的排列 \(P'\)
    • \(vis[i] = 1\) 表示 \(i\) 没有被取出,\(Sum[i]\)\(vis[i]\) 的前缀和 。
    • 令上一次被取出的数是第 \(pos\) 个,当前还剩下 \(cnt\) 个数字,那么下一个被取出来的数是剩下的第 \((pos-1+k -1)\%cnt + 1\)
    • 对于 \(n = 5,k = 3\) 的一次变化如下图
    • img
    img img img img img
    • 对排列 \(\{1,2,3,4,5\}\) 进行一次 \(3- Josephus transform\) 发现每次的 \(pos'\) 实际上就是原数组 \(Sum[i] = pos'\) 的第一个位置( 被取出的数的就是 \(i\) ,因为排列是 \(\{1,2,3,4,5\}\) ,第 \(i\) 位置上的数就是本身)

    • 如果用上述前缀和的形式来得到 \(P'\) 复杂度就是 \(n^2\)

    • 如果考虑用树状数组优化,采用二分判断当前位置的前缀和是否为 \(pos'\) ( 记得是找第一个位置 ),因为树状数组求前缀的复杂度为 \(logn\) 所以整体复杂度就是 \(nlognlogn\) ~~~( 不知道会不会超时 ) ~~~

    • 由于树状数组求一段区间的和是 \(logn\) ,我们还可以考虑用线段树来维护v

    • 若你现在需要找到区间 \([L,R]\) 前缀和为 \(v\) 的第一个位置,如果左子树的 \(Sum\)\(v\) 大,那么你要找的就在左子树上,否则你就要找右子树中 \(v-Sum\) 的那个位置

    • int Query(int t,int v){
          if( l(t) == r(t) )  return l(t);
          if( s(L) >= v )  return Query(L,v);
          else return Query(R,v-s(L));
      }
      
    • 再来考虑一下最后一步快速幂怎么写

    • 我们知道一次置换是 \(T\) ,跟快速幂一样,\(T^2,T^4...\) 都只需要平方一下,那么如果 \(x\) 在二进制下第\(i\)位置上为 \(1\) ,再用对 \(P\) 进行一次 \(T^{1<<i}\) 的置换

  • 代码

    • #include <bits/stdc++.h>
      
      #define L t<<1
      #define R L|1
      #define l(a) ST[a].l
      #define r(a) ST[a].r
      #define s(a) ST[a].sum
      
      using namespace std;
      typedef long long ll;
      typedef pair<int,int> pii;
      const int Maxn = 1e6+10;
      const int Inf = 0x7f7f7f7f;
      const int Mod = 1e9+7;
      const double eps = 1e-7;
      
      struct Segment_Tree{
          int l,r,sum;
      }ST[Maxn << 2];
      
      void Updata(int t){
          s(t) = s(L) + s(R);
      }
      void Build(int t,int ll,int rr){
          l(t) = ll, r(t) = rr;
          if( ll == rr )
          {
              s(t) = 1;
              return ;
          }
          int mid = (ll + rr) >> 1;
          Build(L,ll,mid);
          Build(R,mid+1,rr);
          Updata(t);
      }
      void Change(int t,int pos,int v){
          if( pos < l(t) || r(t) < pos )  return ;
          if( l(t) == pos && r(t) == pos )
          {
              s(t) = v;
              return ;
          }
          Change(L,pos,v);
          Change(R,pos,v);
          Updata(t);
      }
      int Query(int t,int v){
          if( l(t) == r(t) )  return l(t);
          if( s(L) >= v )  return Query(L,v);
          else return Query(R,v-s(L));
      }
      
      int ch[Maxn],per[Maxn],tmp[Maxn];
      void _2(int n){
          for(int i=1;i<=n;i++)   tmp[i] = ch[ ch[i] ];
          for(int i=1;i<=n;i++)   ch[i] = tmp[i];
      }
      void change(int n){
          for(int i=1;i<=n;i++)   tmp[ch[i]] = per[i];
          for(int i=1;i<=n;i++)   per[i] = tmp[i];
      }
      
      int main(){
          int n,m;
          scanf("%d %d",&n,&m);
          for(int i=1;i<=n;i++)  per[i] = i;
          for(int i=1,k,x;i<=m;i++)
          {
              scanf("%d %d",&k,&x);
              Build(1,1,n);
              int pos = 1, cnt = n;
              for(int i=1;i<=n;i++)
              {
                  int Now_pos = (pos-1+k-1)%cnt + 1;
                  pos = Now_pos, cnt--;
                  Now_pos = Query(1,Now_pos);
                  Change(1,Now_pos,0);
                  tmp[i] = Now_pos, ch[i] = i;
              }
              for(int i=1;i<=n;i++)  ch[tmp[i]] = i;
              while( x )
              {
                  if( x&1 )  change(n);
                  _2(n);
                  x >>= 1;
              }
          }
          for(int i=1;i<=n;i++)  printf("%d ",per[i]);
          return 0;
      }
      
      
posted @ 2020-07-30 16:46  HexQwQ  阅读(169)  评论(0编辑  收藏  举报