Game Theory (HackerRank)

この記事は 解説 Advent Calendar 2017の24日目の記事です。

adventar.org

以下のコンテストの解説です。 www.hackerrank.com

はじめに

競プロ!!Advent Calendarのこの記事 (競プロにおけるNim、Grundy数とNimK - かっさのなにか)に 触発されて書きました(というか解いてないことを思い出した)。

以下ではNim, grundy数などの知識を前提としています。 全く知識がない、あるいは不安な方は上の記事を読んでから解く and/or 解説を読むことを推奨します。 またxorを\oplusで表します。 特に記述がなければ、先手(プレイヤー1)と後手(プレイヤー2)が交互に手を打つゲームとし、 どちらのプレイヤーが勝つか出力する問題と思ってください。

Game of Stones

概要

Programming Problems and Competitions :: HackerRank

山に石がN個ある。 プレイヤーは山から2,3,5個いずれかだけ取り除ける。 取り除くことができなくなったプレイヤーの負けである。

解法

メモ化再帰

Tower Breakers

概要

Programming Problems and Competitions :: HackerRank

それぞれに石がM個ある山がN山ある。 プレイヤーは一つの山に石がX個あるとき、Y個に減らすことができる。ただしYXを割り切れなければならない。

解法

M=1のときは明らかに後手勝ち。 M>1とする。 Nが偶数ならば後手は先手と全く同じように操作ができるから後手勝ち。 Nが奇数ならば先手が最初に1つの山を1にすれば、上と同じ戦略が取れるから先手勝ち。

A Chessboard Game

概要

Programming Problems and Competitions :: HackerRank

15\times 15のチェス盤に石が1つ置いてある。 プレイヤーは(x,y)にある石1つを(x-2,y+1), (x-2,y-1), (x+1,y-2), (x-1,y-2)の いずれかに移動させる。ただし盤面外に移動させることはできない。 手を打つことができなったプレイヤーの負けである。

解法

メモ化再帰

Nim Game

普通のNimなので略。

Misère Nim

概要

Programming Problems and Competitions :: HackerRank

N山のNimをする。ただし普通のNimと違うところは 最後に石を取った人が負け (手が打てなくなったプレイヤーが勝ち) という点である。

解法

まずすべての山が1個の石からなるケースを考える。 このときは山の数が偶数なら先手勝ち、奇数なら後手勝ちである。

それ以外のとき、Sを(普通のNimと同じように)すべての山の石の個数の xorをとったものしてS = 0のとき、かつそのときに限り、後手勝ちであることを示す。

今、先手番でS = 0とし、先手が手を打つことでこれがS' \not= 0になったとする。

このとき、後手番ですべての山の石が1となることは無いことが分かる。 もしそうなったとすると、仮定から先手が選んだ山以外の石の数はすべて1であり、 先手が選んだ山はもともと1個より多くの石があったことが分かる。 しかしこのような状況はS=0の下では明らかに起こりえない。

これより、後手番では少なくとも1つの山は1個より多い石がある。 石が1個より多い山がちょうど1つあるときは、その山を01に減らすことで、 山の数が奇数となるようにすべての山を1にする ことができるので後手が勝つ。 そのような山が1つより多くあると、次の先手番ではすべての山が1個の石からなることはない。 従って通常のNimと同じようにしてS'' = 0となるように後手は手を打つことができ、 帰納的に後手勝ちであることがわかる。

逆に先手番でS \not= 0とすると、上の後手番の議論をそのまま適用できるので先手勝ちである。

よってすべての山が1個の石である場合を例外扱いすることで、普通のNimと同じように解ける。

解説だと全部1のときだけ場合分けすればいいとしか書いてないし、 それが正しそうな理由もよく分からなかった。 自分は実験してなんとなく規則性を見つけて適当に投げた。 実験せずに気付けるかと言われると微妙。

Nimble Game

概要

Programming Problems and Competitions :: HackerRank

N個の箱が一列に並んでいて、0,1,2,\dotsと番号が付いている。 各箱には石がc_i個入っており、プレイヤーはi番目の箱にある石1つを箱j \lt iに移すことができる。 先に手を打つことができなくなったプレイヤーの負けである。

解法

位置iにある石はそれより小さいj \lt iのどこへでも動かせる。 これはNimの1つの山にある石の個数がi個であるときの状況とちょうど対応している。 よってS = \displaystyle\bigoplus_{c_i \bmod 2 = 1}{i}0なら後手勝ちである。

Poker Nim

概要

Programming Problems and Competitions :: HackerRank

N山にそれぞれc_i個石がある。 プレイヤーは各ターンで1つの山から1個以上の石を取り除くか、あるいは追加することができる。 ただし、石を追加できる回数は各プレイヤー、各山に対して高々K回までである(個数は任意)。 最後の石を取り除いたほうが勝ちである。

解法

石を追加しないNimと全く同じ結果になる。 つまり、S = \bigoplus_i c_i = 0のとき、かつそのときに限り、後手必勝となる。 S=0のときは先手が石を取り除こうが追加しようが、後手は先手と同じことをすればNim和は0になる。 また石の追加は高々K回までしかできず、必ず有限回で終わるので後手必勝。

S \not= 0のときは適当に石を取り除いてS = 0の状態に持っていけるので先手必勝である。

Tower Breakers, Revisited!

概要

Programming Problems and Competitions :: HackerRank

N山にそれぞれh_i個石がある。 プレイヤーは1つの山にあるX個の石をY \lt Xまで減らすことができる。ただしY \geq 1Xの 約数でなければならない。 最後の石を取り除いたほうが勝ちである。

解法

山が1つでx個石があるとする。 このときのgrundy数がどうなるか考える。 xのある約数dgrundy数をg_dとすると、dの任意の約数d'にも xから遷移できるので、xgrundyg_xは少なくともg_d + 1以上である。 これをxのすべての約数にわたって考えればg_x = \displaystyle \max_{d \nmid x}{(g_d+1)}となる。

よくよく考えるとこれはxの重複を含めた素因数の個数(例えばx = 2^2\times3^1なら2+1=3)と一致することがわかる (素因数pxを割ったものならどれでも最大になるため) ので、各h_iのこれを調べて最後にそれらのNim和をとればよい。

Tower Breakers, Again!

概要

Programming Problems and Competitions :: HackerRank

N山にそれぞれh_i個石がある。 プレイヤーは1つの山にあるX個の石を、Z個の石からなるY個の山に分けることができる。 ただしY > 1, Y \times Z = Xでなければならない。 最後の石を取り除いたほうが勝ちである。

解法

基本的に1つ前の問題と同じで重複を含めた素因数の個数がgrundy数となる。 ただし2を素因数にもつときは一度だけしか数えない(2^iのグランディ数が1となるから)。

Chessboard Game, Again!

概要

Programming Problems and Competitions :: HackerRank

15\times 15のチェス盤にいくつか石が置いてある。 1つのマスに石が複数置いてあっても良い。 プレイヤーは(x,y)にある石1つを(x-2,y+1), (x-2,y-1), (x+1,y-2), (x-1,y-2)の いずれかに移動させる。ただし盤面外に移動させることはできない。 手を打つことができなったプレイヤーの負けである。

解法

素直にgrundy数を計算すれば良い。 盤面が15 \times 15で、ありうる遷移が高々4なので愚直に計算しても十分に間に合う。 位置(x,y)から動かせる手(x',y')についてx+y > x'+y'が成り立つから、 x+yが小さいものから順に計算することができる。

Digits Square Board

概要

Programming Problems and Competitions :: HackerRank

N \times Nのマスに整数x (1 \leq x \leq 9)が書かれている。 プレイヤーは盤面の面積が1より大きく、かつ素数でないような数字が少なくとも1つ書かれているとき、 盤面を水平方向か垂直方向に2つに分割することができる。 手を打つことができなくなったプレイヤーの負けである。

解法

素直にgrundy数を計算すれば良い。 dp(x_1,y_1,x_2,y_2) = \text{「元の盤面の部分長方形で左上が}(x_1,y_1)\text{で右下が}(x_2,y_2)\text{のgrundy数」} でメモ化再帰する。 素数でないものが部分長方形に入っているかを累積和で前計算しておけば、盤面が分割できるかどうかは\mathcal{O}(1)で判定できる。 上の状態が\mathcal{O}(N^4)あり、各状態からの分割方法は\mathcal{O}(N)あるので、 全体で\mathcal{O}(N^5)で計算できる。

Fun Game

概要

Programming Problems and Competitions :: HackerRank

長さN2つの数列A=(a_1,\dots), B=(b_1,\dots)がある。 プレイヤー1Aから、プレイヤー2Bから交互に数字を選んでいく。 ただしa_iがすでに選ばれていたらb_iを選ぶことができず逆も同様。 なのでちょうどN回の操作でゲームは終わる。

各プレイヤーが選んだ数の総和を計算し、大きい方が勝ち、同じなら引き分けである。

解法

この手の2つの数列から片方を選ぶみたいな問題は適当な評価値を決めて、 大きい方から選んだりdpしたりすることが多い気がするのでそうする。

このゲームは次のようなゲームに置き換えられる。

  • プレイヤー1は初期点として\sum_i{a_i}を持っている。
  • 数列C = (a_1+b_1, \dots) がある。
  • プレイヤー1Cから数字を一つ消すことができ、プレイヤー2は選んだ数だけプレイヤー1の点数を減らせる。
  • 最後にプレイヤー1の点数が正ならばプレイヤー1の勝ち、0なら引き分け、負ならプレイヤー2の勝ち。

なのでCを計算、ソートして、数字が大きいものから順番に消す、点数を減らすことを交互にすればよい。

Powers Game

概要

Programming Problems and Competitions :: HackerRank

文字列\heartsuit 2^1 \heartsuit 2^2 \heartsuit \dots \heartsuit 2^nがある。 プレイヤーは交互に\heartsuit+-のどちらかに1つずつ書き換える。 最後にすべての\heartsuitが無くなったら数式を計算し、17で割りきれたら後手勝ち、そうでなければ先手勝ち。

解法

x_i = 2^i \bmod 17だけを気にすればいいから、現れる数字は実質0 \leq x_i \lt 17だけとみなせる。 また、x_i = x_j (i \not= j)なるものがあれば、お互いに打ち消すように手を指すことができる (片方が+に書き換えたらもう片方は-に変える)ので、奇数個あるものだけ着目すれば良い。

あとはdp(i,r,B) = \text{「プレイヤー}i\text{が今までの和が}r\text{であり、残り使える集合が}B\text{から始めるときに勝てるか」} のようにBをbitとして使いメモ化再帰で解ける。 n \leq 10^6なのでn=1から順番に前計算しておけば良い。

Deforestation

Programming Problems and Competitions :: HackerRank

既出。同じ問題がhttps://beta.atcoder.jp/contests/agc017/tasks/agc017_dにある。 ハッケンブッシュ(Hackenbush)というゲームがあってそれの特殊な形である。

Tower Breakers - The Final Battle

概要

Programming Problems and Competitions :: HackerRank

N個の石がある。N=1となるまでプレイヤーは以下の操作を交互にする。

  • プレイヤー1N = n_1 + n_2 + \dots + n_k,\; k \geq 2となるように自由に石を分割する。
  • プレイヤー2はこの中からi番目の山を選び、N = x_iとしてゲームを続ける。このときコインi^2枚を得る。

プレイヤー1はプレイヤー2が得られるコインの枚数を最小化、プレイヤー2は最大化しようとする。 このとき最終的に得られるコインの枚数はいくらか。

解法

小さいケースで実験するとNがある程度大きくなっても答えは大きくならないと予想ができる。

そこでf(m) = \text{「答えがmとなるような最大のN」}とする。 明らかにfは単調増加になるから、f(m) \leq Nとなるような最大のmが求める答えである。 i番目の山を選ぶとコインをi^2払うことになるから、 f(m) = \displaystyle\sum_{m-i^2\geq 0}{f(m-i^2)}となる。 m=121f(m) \gt 10^{18}となるから愚直に計算しても実行時間は十分間に合う。