K – Stones 解説 (Educational DP Contest / DP まとめコンテスト)
問題概要
二人で以下のゲームを行う。先手と後手のどちらが勝つか?
- 以下の操作を交互に行う
- A = \( a_1, a_2, \ldots, a_N\) の元 x を一つ選び、K 個の山から丁度 x 個取り除く
- 先に取り除けなくなったほうが負け
制約
- \(1 \leq N \leq 100\)
- \(1 \leq K \leq 10^5\)
- \(1 \leq a_1 < a_2 < \cdots < a_N \leq K\)
考え方
ゲームに関する問題です。後退解析という考え方が使えます。
後退解析とは、「ゲームの終了状態から出発して、ゲームの開始状態まで逆方向に解析していくこと」です。
具体的に考えましょう。残りが0個の時はその時の手番の人の負けです。ここから逆に考えると、
- 残りが \(a_1\) 個の時は \(a_1\) 取り除けば勝てる
- 残りが \(a_2\) 個の時は \(a_2\) 取り除けば勝てる
- 残りが \(a_3\) 個の時は \(a_3\) 取り除けば勝てる
- …
- 残りが \(a_N\) 個の時は \(a_N\) 取り除けば勝てる
となり、1つ逆方向に戻って勝ち負けを判定することができるのです。ここからどんどんと戻りながら考えていきますが、普通にやると N 個ずつ分岐していってしまい、計算量が膨大になってしまいます。
そこで、後退解析は以下のようにDPを使うことで高速に計算することができます。
dp[ i ] := 残りが i 個の時に、その手番の人が勝てるかどうか
初期化
- dp[ i ] := false
更新
配る dp で考えると以下のようになります。
- dp[ i + a[ j ]] = dp[ i ] が false なら true
もらう dp で考えると以下の通りです。
- dp[ i ] = dp[ i – a[ j ] ] に false が存在するなら true
意味的には後者のほうが分かりやすいかもしれません。相手が負けるような遷移先が存在するなら、そのように遷移するのが最善手です。
実装例
#include <bits/stdc++.h> #define LOOP(n) for (int _i = 0; _i < (n); _i++) #define REP(i, n) for (int i = 0; i < (n); ++i) #define FOR(i, r, n) for (int i = (r); i < (n); ++i) #define ALL(obj) begin(obj), end(obj) using namespace std; using ll = long long; using ull = unsigned long long; int N, K; bool dp[200005]; int main() { // cin.tie(0); // ios::sync_with_stdio(false); cin >> N >> K; vector<int> A(N); REP(i, N) cin >> A.at(i); REP(i, K) { REP(j, N) { if (!dp[i]) dp[i + A[j]] = true; // 配るdp } } if (dp[K]) { cout << "First" << endl; } else { cout << "Second" << endl; } return 0; }
ディスカッション
コメント一覧
まだ、コメントがありません