TopCoder SRM 561 Div1 Medium CirclesGame
問題
座標平面上に円がいくつかある。
i番目の円の中心は(x[i], y[i])で、半径はr[i].
円同士が共有点を持つことはない。(ある円がある円の内部にあることはある)
AliceとBobが次のようなゲームをする。
- Aliceが先手。交互に手番をもつ。
- 手番のプレイヤーは、内部に赤い点の含まれて居ない円を一つ選ぶ。
- 選んだ円の真に内部に赤い点を打つ。
- 点が打てなかったプレイヤーが負け。
お互いが最善を尽くすとき、どちらのプレイヤーが勝つかを求めよ。
制約条件
n≦50
方針
r = 0の円は消して考える。
包含関係でグラフを作ると、木になる。
円を選んで内部に赤い点をうつという操作は、
木から一つの頂点を選び、その点から、頂点までのパスを全て消す、
という操作に対応する。
ゲームの勝者はgrundy数を使って求めればいい。
部分木は、根を指定すれば一意に定まる。
パスを取り除いた後、森にわかれるが、それぞれの木のgrundy数のxorを取ればいい。
ソースコード
inline int d2(int x, int y){ return x * x + y * y; } bool contain(int x, int y, int r, int X, int Y, int R){ if(r <= R) return 0; return d2(x - X, y - Y) < (r - R) * (r - R); } int n; int e[60][60], can[60][60]; int dp[100000]; int grundy(int p){ if(dp[p] >= 0) return dp[p]; set<int> s; //i: 取り除く頂点(部分木のどれかでなければならない) rep(i, n) if(can[i][p]){ int tmp = 0; bool use[60] = {}; //pからiへのパス上の頂点は全て取り除かれる rep(j, n) if(can[i][j]) use[j] = 1; //残った森を見る。それぞれの木ごとにgrundy数を求め、xorを取る。 rep(j, n) if(!use[j] && can[j][p]){ int r = j; while(1){ int nr = -1; rep(k, n) if(!use[k] && e[r][k]) nr = k; if(nr < 0) break; r = nr; } rep(k, n) if(can[k][r]) use[k] = 1; tmp ^= grundy(r); } s.insert(tmp); } for(int i = 0; ; i++) if(!s.count(i)) return dp[p] = i; } class CirclesGame { public: string whoCanWin(vector <int> x, vector <int> y, vector <int> r) { rep(i, x.size()) if(r[i] == 0){ x.erase(x.begin() + i); y.erase(y.begin() + i); r.erase(r.begin() + i); i--; } memset(dp, -1, sizeof(dp)); memset(e, 0, sizeof(e)); memset(can, 0, sizeof(can)); n = x.size(); //n番という全部の森の親のダミーの頂点を作ると楽。 rep(i, n) can[i][n] = e[i][n] = 1; rep(i, n) rep(j, n){ if(contain(x[i], y[i], r[i], x[j], y[j], r[j])) can[j][i] = e[j][i] = 1; } //a→b→cという関係があったら、a→c間の辺は取り除く。 rep(i, n + 1) rep(j, n + 1) if(e[i][j]){ rep(k, n + 1) if(can[i][k] && can[k][j]) e[i][j] = 0; } rep(i, n + 1) can[i][i] = 1; return grundy(n) ? "Alice" : "Bob"; } };