コードは意外に短い。
https://yukicoder.me/problems/no/3258
問題
整数列の多重集合Sがある。
Sには初期状態として、1つの整数列Fが入っており、入力で与えられる。
以下の2人ターン制ゲームを行う。
- Sから数列を選ぶ。この数列をAとしたとき、1つindex iを指定する。
- その後、SからAを取り除いて、2つの数列(A[0] xor A[i], A[1] xor A[i], ... , A[i-1] xor A[i])と(A[i+1] xor A[i], A[i+2] xor A[i], ... , A[|A|-1] xor A[i])をSに追加する。(ただし空となる数列は追加しない)
- その際、0を要素に持つ数列ができた場合、その人の負けである。
両者最適手を取ったとき、勝者はどちらか。
解法
xor云々を考えるとややこしいが、結局このゲームはAのうち2回以上登場する値は選択できないことになる。
また、最終的な状態は手順を問わず同じになる。
よって、初期状態から貪欲に数列・indexを選ぶとき、操作できる処理の偶奇を考えればよい。
初期の数列の区間に対して貪欲にindexを選ぶ場合、左右同時に少しずつ範囲を広げて調べて行くとよい。
そうすると同じ位置を何度も調べることを避けることができ、O(N^2)に見えてO(NlogN)で済む。
int N; ll F[202020]; vector<int> pos[202020]; int num(int v,int L,int R) { // F{L]...F[R-1]におけるvの数 return lower_bound(ALL(pos[v]),R)-lower_bound(ALL(pos[v]),L); } int dfs(int L,int R) { //削除できる回数 for(int TL=L,TR=R-1;TL<=TR;TL++,TR--) { if(num(F[TL],L,R)==1) { return dfs(L,TL)+dfs(TL+1,R); } if(num(F[TR],L,R)==1) { return dfs(L,TR)+dfs(TR+1,R); } } return R-L; } void solve() { int i,j,k,l,r,x,y; string s; cin>>N; vector<int> Fs; FOR(i,N) { cin>>F[i]; Fs.push_back(F[i]); } sort(ALL(Fs)); FOR(i,N) { F[i]=lower_bound(ALL(Fs),F[i])-Fs.begin(); pos[F[i]].push_back(i); } int num=N-dfs(0,N); if(num%2) { cout<<"Alice"<<endl; } else { cout<<"Bob"<<endl; } }
まとめ
左右から調べて行くテク、たまに使うけどそのたびに忘れる…。