TopCoder SRM 565 Div1 Medium TheDivisionGame
問題
二人のプレイヤーがn個の山を使って次のようなゲームをする。
- 手番のプレイヤーは、山をひとつ選ぶ。
- この山の石の数をXとする。Xより小さいXの約数Yを選び、この山の石の数をYに変える。
- 操作のできなくなったプレイヤーの負け
最初の山のうち[A, B]の形になっているものを考える。
すなわち、A, A+1, A+2, ..., Bを最初の山とする。
L, Rが与えられたとき、L≦A≦B≦Rを満たし、ゲームにおいて両者最善を尽くしたとき先手が勝ちになるようなA, Bの組はいくつあるか求めよ。
制約条件
L, R≦10^9
R≦L + 10^6
方針
ひとつの山を見ると、操作は、Xの約数の個数を1つ以上の好きな数を減らすことであることがわかる。
すなわち、このゲームは約数の個数でnimをしていることに他ならない。
なので、[A, B]のxorが0にならないA, Bの個数を数えればよい。
これは、[A, B]のxorが0になるA, Bの個数を区間全部の個数から引くことで求める。
まずは[L, R]のそれぞれの数の約数を求める。
これは区間ふるいっぽくやればよい。
そしたら、xorでの累積和を求める。
[A, B]のxorが0のとき、[L, A - 1]のxorと[L, B]のxorが等しい。
すなわち、Aを左端とする、xorの区間の個数は、
A≦i≦Rを満たすiのうち、[L, i]のxorが[L, A - 1]のxorに等しくなるようなiの個数に等しい。
これは累積和を計算しておけば効率的に求められる。
ソースコード
int p[100000]; int a[1100000], b[1100000]; int cnt[1000]; class TheDivisionGame { public: long long countWinningIntervals(int L, int R) { int len = R - L + 1; ll ans = len * (len + 1ll) / 2; memset(cnt, 0, sizeof(cnt)); memset(b, 0, sizeof(b)); rep(i, len) a[i] = i + L; for(int i = 2; i * i < 100000; i++) if(!p[i]) for(int j = i * i; j < 100000; j += i) p[j] = 1; for(int i = 2; i * i <= R; i++) if(!p[i]){ for(int j = (L + i - 1) / i * i; j <= R; j += i){ while(a[j - L] % i == 0){ a[j - L] /= i; b[j - L]++; } } } rep(i, len) if(a[i] > 1) b[i]++; int x = 0; rep(i, len){ x ^= b[i]; cnt[x]++; } x = 0; rep(i, len){ ans -= cnt[x]; x ^= b[i]; cnt[x]--; } return ans; } };