Official

I - 円陣パスゲーム / Circle Pass Game Editorial by physics0523


便宜上、子どもの番号を \(0\) 始まりであるとして説明します。

円陣に残っている子どもが \(a\) 人であるとき、 \(D\) 番の指す子どもと \(D-a\) 番の指す子どもは同じです。
なので、初めから \(D\)\(a\) で割った余りで \(D\) を置き換えて構いません (ただし、その結果 \(D=0\) となった場合は \(D=r\) であるとします)。

これで、求めるべきは「残っている子どもの中で、 \(x\) 番から始めて \(d\) 人目の子ども」となります。
これを高速に求めるにはデータ構造を活用する必要があります。以下に一例を示します。

  • 長さ \(2N\) の区間和 segment tree を用意する。
  • この segtree は、 \(i\) 番の子どもがまだ残っている時に \(i,N+i\)\(1\) 、そうでないときに \(0\) とする。
  • segment tree 上の二分探索 (ACL では標準実装されています) を使って、和が \(d\) 未満となる区間 \([x,r)\) のうち \(r\) が最大のものを求める。
  • この \(r\) が求める子どもの番号である。ただし、 \(r \ge N\) であるときは求める子どもの番号は \(r-N\) である。

なお、本解法は円環 \(0,1,\dots,N-1\) 上での処理を、 \(0,1,\dots,N-1,0,1,\dots,N-1\)\(2\) 周回して \(1\) つの区間での処理に言い換えるという小技を利用しています。

時間計算量は \(O(N + M \log N)\) です。

実装例 (C++):

#include<bits/stdc++.h>
#include<atcoder/all>

using namespace std;
using namespace atcoder;

int op(int a,int b){ return (a+b); }
int e(){ return 0; }

int target;
bool f(int x){ return (x<target); }

int main(){
  int N,M,S;
  cin >> N >> M >> S;
  int x=(S-1);
  vector<int> ini(2*N,1);
  segtree<int,op,e> seg(ini);
  int remain=N;
  while(M--){
    seg.set(x,0);
    seg.set(x+N,0);
    remain--;
    int D;
    cin >> D;
    D%=remain;
    if(D==0){D=remain;}
    target=D;
    x=seg.max_right<f>(x)%N;
  }
  cout << x+1 << "\n";
  return 0;
}

posted:
last update: