一目見て惹かれた。すごく面白かった。割と自然な考察の流れで解けた。
問題概要
個の正の整数からなる数列 が与えられる。先手と後手が交互に以下を繰り返す。先に操作を行えなくなった方が負けである。双方最善を尽くしたときにどちらが勝つか?
- 以上の整数を 1 つ選んで、1 減らす。その後 個の整数の最大公約数を として、各整数を で割る
制約
- 初期状態では 個の整数の最大公約数は
考えたこと
面白そう!!!
こういうのは小さいケースで具体的に手を動かしてみると自然に解ける気がする。
のとき、 だったら負け、 だったら勝ち
のとき、 の形だったら、 が偶数だったら負け、奇数だったら勝ち、というのはすぐにわかる。また同時に、 の中に 1 個でも 1 があったら、そこから先は最大公約数はかならず 1 なので、残りの の総和のパリティのみで決まってしまう。
ここまで考えてみると、パリティの考察が大事な問題に思えて来る。最大公約数が偶数か奇数かが結構命運を分けるように思える。
最大公約数が奇数のとき、元々奇数だったものは奇数に、偶数だったものは偶数になることから、最大公約数で割ることによって の総和のパリティは変化しないことがわかる!!!
つまり、もし の総和のパリティが奇数の盤面が与えられた状態から、相手に
- の総和のパリティが偶数
- の中に奇数が 2 個以上ある
という盤面を渡せたならば、相手はどうやっても最大公約数が奇数になる手しか打てないので、こちらにはまた総和のパリティが奇数の盤面が渡って来るので、これを繰り返して勝ち確になる。この対称戦略がこのゲームの肝になりそう。
詰める
いよいよここから詰める!!!
まず の中に 1 がある場合は勝敗が決まっているので、1 はないものとする。また、すべて偶数ということはありえない (最大公約数が 1 なため) ので、奇数は 1 個以上含まれる。
奇数が 2 個のとき
比較的明快。
- の総和が奇数のとき、相手に「奇数が 1 個だけ」という盤面を与えないように注意すれば、勝ち確定
- の総和が偶数のとき、どうやっても最大公約数が奇数にしかできないので、負け確定
奇数が 1 個のとき
の総和が奇数のときは簡単。相手に「奇数が 1 個だけ」という盤面を与えないように注意すれば勝ち確定。
問題は の総和が偶数のとき。
しかしよく考えると、このときに打てる手はもう
- が奇数となっている部分を 1 減らして、最大公約数を偶数にする
しか勝ち目がないことがわかる。それ以外の手はすべて相手勝ちになるからだ。つまりこの問題は
選べる手が実質的に1通りしかない
ということになる。よって再帰的に Greedy に手を打っていくことで勝敗が確定する。
#include <iostream> #include <vector> using namespace std; long long GCD(long long a, long long b) { return b ? GCD(b, a%b) : a; } bool solve(vector<int> A) { long long sum = 0; int odd = 0; bool ichi = false; for (int i = 0; i < A.size(); ++i) { sum += A[i] - 1; if (A[i] & 1) ++odd; if (A[i] == 1) ichi = true; } if (ichi) { if (sum % 2 == 0) return false; else return true; } else if (odd > 1) { if (sum % 2 == 0) return false; else return true; } else { if (sum % 2 == 1) return true; for (int i = 0; i < A.size(); ++i) if (A[i] & 1) --A[i]; long long g = A[0]; for (int i = 0; i < A.size(); ++i) g = GCD(g, A[i]); for (int i = 0; i < A.size(); ++i) A[i] /= g; if (!solve(A)) return true; else return false; } } int main() { int N; cin >> N; vector<int> A(N); for (int i = 0; i < N; ++i) cin >> A[i]; if (solve(A)) puts("First"); else puts("Second"); }