Codeforces #156 Div1 C (256 C) Furlo and Rublo and Game

問題

n個のコインの山があり、それぞれa[i]枚のコインが積まれている。
この山を使って二人が次のようなゲームをする。

  • 交互に手番をもつ。手番に操作のできなくなったプレイヤーが負け。
  • 手番のプレイヤーは山をひとつ選ぶ。この山のコインの数をxとする。
  • 手番のプレイヤーは選んだ山のコインの数をx^(1/4)≦y≦x^(1/2)を満たす整数yに変える。
  • x=yとなるようなyを選ぶことはできない。


お互いが最善を尽くしたとき、勝利するプレイヤーはどちらか、求めよ。

制約条件

n≦77777
a[i]≦777777777777

方針

grundy数を求める。
のだけど、愚直にメモ化再帰するとTLE.


実験してみると、i枚の山のgrundy数をgrundy(i)としたとき、
grund(i)が変化するのは、iが平方数のすぐ近くでのみなことがわかる。
なので、iを平方数のすぐ近くまで寄せてしまうと、結構速い時間でgrundy(i)が求まる。


それで、複数のa[i]についてgrundy数を求めなければいけないので、
山を小さい順にソートして、尺取するようにする。


以上の工夫で700msくらいで通る。

ソースコード

map<int, int> dp;
int rec(int n){
	int m = sqrt(n);
	n = min(n, m * m + 2);
	if(dp.count(n)) return dp[n];
	
	int b = sqrt(n), a = (int)ceil(sqrt(sqrt(n * 1.0)) - EPS);
	
	set<int> s;
	for(int i = a; i <= b; i++){
		if(i < n) s.insert(rec(i));
	}
	for(int i = 0; ; i++){
		if(!s.count(i)) return dp[n] = i;
	}
}

int cnt[100];
ll in[100000];

int main(){
	
	int n;
	cin >> n;
	rep(i, n) cin >> in[i];
	sort(in, in + n);
	
	int ans = 0;
	int l = 0, r = 0;
	
	rep(i, n){
		int a = (int)ceil(sqrt(sqrt(in[i] * 1.0)) - EPS);
		int b = sqrt(in[i]);
		if(b >= in[i]) b--;
		a = min(a, b + 1);
		
		while(r <= b) cnt[rec(r++)]++;
		while(l < a) cnt[rec(l++)]--;
		
		for(int j = 0; ; j++) if(!cnt[j]){
			ans ^= j;
			break;
		}
	}
	
	cout << (ans ? "Furlo" : "Rublo") << endl;
	return 0;
}