ゲーム問題が苦手すぎる・・・・

CPSCO2019 Session2 D Two Piles

問題概要

A枚のコインがある 1 つの山と、 B 枚のコインがある 1 つの山がある。 この2つの山を使ってAliceとBobがゲームをする。Aliceを先手として、2 人は以下の操作を交互に繰り返す。

  • 1枚以上のコインがある山を選ぶ。この山の枚数をXとする
  • その後2つの山から合計でX枚のコインを取り除く

どちらの山にもコインがなくなったときの操作を終了し、最後に操作した人が勝者となる。

制約

1\leq A,B \leq 10^{5}

考察

  • サンプルだけでは分からないので実験してみる
  • 以下に例と最適に行動したときの遷移を示す。
  • 例1 初期状態が(A,B) = (1,1)の時、この後は (1,0)(Alice)(0,0)(Bob)となり勝者はBob
  • 例2 初期状態が(A,B) = (2,2)の時、この後は (1,1)(Alice)(1,0)(Bob)(0,0)(Alice)となり勝者はAlice
  • 例3 初期状態が(A,B) = (3,3)の時、この後は (1,2)(Alice)(1,1)(Bob)(0,1)(Alice)(0,0)(Bob)となり勝者はBob
  • どうやら片方の山の枚数が1枚になるとその後は1枚ずつ取るのが最適になるっぽい
  • そして片方が1枚になった時、もう片方の枚数が奇数ならば、相手に負けの状態を押し付けられるのでAliceが勝者となる(1枚の山の枚数も数えて残り枚数が偶数ならば勝利と考えても良い)(実験より分かったこと)ということで、先手が最初に片方の山を1枚にする操作をしてみる。
  • ここではまず、山Aを選び、Aが1枚になるように取り(つまりA-1枚取る)、Bからは1枚取るとする(Bを1枚にしてAから1枚取る操作も同様に説明できる)
  • この時取るコインの枚数は(A-1)+1 = A枚となり、山Aを選んだ時に取る枚数の条件であるA枚を 満たしている。さて、この時、山Aが1枚になっているので残りは交互に1枚ずつ取ることになる。これは残り枚数の奇偶で判定できる。上述したように、残り枚数は山Aの1枚と山BB-1枚なので、残り枚数の合計は1+ B-1 = B枚となる。つまり、操作後のB枚が偶数の場合、相手に負けの状態を押し付けられるので先手(Alice)は勝利できる。一方奇数の場合は先手は敗北する。
  • Bを選ぶ場合も考慮すると、最終的にはABの奇偶判定をすれば良いことになる
  • 以上をまとめると、ABのどちらかが偶数ならば先手必勝、それ以外は後手必勝となる。

ソースコード

※マクロは使っていないので無視してください

#include<iostream>
#include<vector>
#include<algorithm>
#include<cctype>
#include<utility>
#include<string>
#include<cmath>
#include<cstring>
#include<queue>
#include<map>
#include<set>

#define REP(i, n) for(int i = 0;i < n;i++)
#define REPR(i, n) for(int i = n;i >= 0;i--)
#define FOR(i, m, n) for(int i = m;i < n;i++)
#define FORR(i, m, n) for(int i = m;i >= n;i--)
#define SORT(v, n) sort(v, v+n);
#define VSORT(v) sort(v.begin(), v.end());
#define llong long long
#define pb(a) push_back(a)

using namespace std;
typedef pair<int, int> pii;

typedef long long int ll;

template<typename T>
vector<T> make_v(size_t a) { return vector<T>(a); }
template<typename T, typename... Ts>
auto make_v(size_t a, Ts... ts) {
	return vector<decltype(make_v<T>(ts...))>(a, make_v<T>(ts...));
}
template<typename T, typename V>
typename enable_if<is_class<T>::value == 0>::type
fill_v(T& t, const V& v) { t = v; }
template<typename T, typename V>
typename enable_if<is_class<T>::value != 0>::type
fill_v(T& t, const V& v) {
	for (auto& e : t) fill_v(e, v);
}


#define ARRAY_MAX 1005
const ll INF = 1e9 + 7;

int main() {

	int a, b;
	cin >> a >> b;

	if ( (a % 2) == 0 || (b % 2) == 0)
	{
		cout << "Yes" << endl;
	}
	else
	{
		cout << "No" << endl;
	}

	return 0;
}