TopCoder SRM 419 Div1 Medium NimForK
問題
k人がn個の石を使って次のようなゲームをする。
- k人は時計回りに並び、1番の人から時計回りに順にターンをまわす。
- ターンの人は山から石を取る。このとき取れる石の個数は下の条件を満たす必要がある。
- 山の石がなくなったときゲーム終了。最後に石を取った人が勝ち。
- 山に石があるのに、それ以上石を取れなくなったときは、勝者なしでゲーム終了。
山の石がi個のときに取れる石の個数はmoves[i][]のどれか。
それぞれの人は以下の戦略で石を取る。
- 他の人は全員自分と同じ戦略を取っているとする。
- 他の人がどのように石を取っても自分が勝つ手があるときは、その手を選択する。
- 自分が勝つ確率が0でない手があるとき、その手を選ぶ。それが複数ある場合、その中から一つをランダムで選ぶ。
- 自分が負ける手しかないとき、その手を選ぶ。複数ある場合はその中からランダムで一つ選ぶ。
moves[i][]が与えられたとき、勝つ確率がある人の番号を全て出力せよ。
制約条件
n≦50
k≦20
方針
動的計画法による。
dp[i][j]を、山に石がi個残っていて、現在の手番がjの人のときに、
勝てる確率のある人の集合とする。
これを更新していくようなDPをすればいい。
一つ注意する必要があるのは、
「i番目の人が必ず勝つ」という状態と「i番目の人のみ、勝つ確率がある」という状態を区別する必要があるということ。
後者は、「i番目の人が勝つ、または引き分け」という状態である。
ソースコード
int k; vector<vi> ms; int dp[100][100]; int rec(int n, int cur){ int &res = dp[n][cur]; if(res >= 0) return res; if(n == 0){ int w = (cur + k - 1) % k; return res = 1 << w; } res = 1 << k; bool f = 1; rep(i, ms[n - 1].size()){ int tmp = rec(n - ms[n - 1][i], (cur + 1) % k); if(tmp == 1 << cur) return res = 1 << cur; if(tmp & 1 << cur){ if(res & 1 << cur) res |= tmp; else res = tmp; } else{ if(!(res & 1 << cur)){ if(f) res = tmp; else res |= tmp; } } f = 0; } if(res < 0) res = 0; return res; } class NimForK { public: vector <int> winners(int n, int k, vector <string> moves) { rep(i, 100) rep(j, 100) dp[i][j] = -1; ms.clear(); ms.resize(n); rep(i, n){ stringstream ss(moves[i]); int t; while(ss >> t) ms[i].pb(t); } ::k = k; int ans = rec(n, 0); vi v; rep(i, k) if(ans & 1 << i) v.pb(i + 1); return v; } };