组合游戏。如果两位球员都发挥得最好的话,谁赢了

时间:2015-01-22 23:55:54

标签: algorithm game-theory

  

玩家 A B 以最佳方式玩游戏并交替移动。他们   从1开始。每个玩家轮流乘以当前的数字   来自[2,9]的任何整数。如果在玩家回合后,数字是   大于或等于 n ,他赢了。

     

A开始。鉴于 n ,谁赢了?

例如,

数字2,3 ..,9是中奖号码(玩家A将获胜)

数字10,11,......,18正在输掉数字(玩家A将输掉)

数字19,20,..,162是中奖号码

获胜策略是什么?如何应用Sprague-Grundy定理来解决这个问题?

2 个答案:

答案 0 :(得分:2)

根据Sprague-Grundy theorem,可以为公正游戏的每个状态分配一个名为 Grundy number 的非负整数,这样在这个状态下移动的玩家将失去iff这个数字是0,如果此数字不为零,则获胜。

如果状态的Grundy数字已知,则获胜策略是始终转移到Grundy数为0的状态。

计算一般游戏状态的Grundy数的算法如下:

if current player can't make a valid move:
    Grundy number := 0 (this player has lost)
else:
    for each move in this state:
        for each sub-game the game splits into after that move:
            compute Grundy number of the sub-game
        compute XOR of Grundy numbers of the sub-games
    Grundy number := MEX of those XORs

MEX是最小的排除函数。一组非负整数的MEX等于最小的非负整数,不属于该集合。

例如:

MEX(0) = 1
MEX(0, 1) = 2
MEX(0, 2) = 1
MEX(0, 1, 2) = 3
MEX(0, 1, 3) = 2
MEX(1, 2, 3) = 0
MEX(10, 100, 1000) = 0

在Python 3中对这个游戏的这个算法的朴素实现可能如下所示:

import functools
from itertools import count

def mex(s):
    for i in count():
        if i not in s:
            return i

@functools.lru_cache(10000)
def sprague_grundy(n, cur=1):
    if cur >= n:
        return 0
    move_results = {sprague_grundy(n, cur*move) for move in range(2, 9+1)}
    return mex(move_results)

for i in count(1):
    print(sprague_grundy(i))

理解Grundy数字通用公式的最简单方法通常是查看序列并尝试注意关系。 在这个游戏中,您可以通过简单地查看玩家A在初始状态中获胜的游戏的 n 数字来计算出一般公式,而无需实际计算Grundy数字。

但是我们仍然可以看看连续 n 的游戏初始状态的Grundy数量(0表示玩家A在初始状态下失败,1,2,3,4意味着玩家A获胜):

$ python3 sprague_grundy.py | uniq -c
     1 0
     1 1
     2 2
     4 3
     1 4
     9 0
    18 1
    36 2
    72 3
    18 4
   162 0
   324 1
   648 2
  1296 3
   324 4
  2916 0

可以注意到,对于玩家A,所有失败的初始状态都是

n \in \left( 9\cdot18^k .. 18^{k+1} \right ]: \textup{for} : k=0,1,2,...

或者换句话说,玩家A的初始状态正在失去iff

\left\lfloor{\log_{18}{\frac{n-1}{9}}}\right\rfloor > \left\lfloor{\log_{18}{\frac{n}{18}}}\right\rfloor

答案 1 :(得分:1)

基本上你创建一个数组 A [] ,其中 A [i] 存储数字 i 是胜利的位置还是相对于开始游戏的玩家。让玩家 A 。基本规则,从失败的位置,你只能去一个胜利的位置和一个获胜的位置是这样的,总是有一个失败的位置可以从它。可以解释代码( 1 意味着赢得了< strong> A 和 0 表示失败。

       for each i from 1 to 9
           A[i]=1
       for each i from 10 to n
           flag=0
           A[i]=0
           for each j from 2 to 9
               if i is divisible j and A[i/j] is 0
                  flag=1
           if flag is 1
               A[i]=1

现在,如果 A [n] 1 ,那么他就输了他就输了。
这是一个 O(n)解决方案,包括时间和内存。你可以减少内存,但是 时间我无法想出更好的解决方案。可能有一个 O(1)解决方案,但我不知道它。