Official
I - 円陣パスゲーム / Circle Pass Game Editorial
by
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:
