计算减法游戏的获胜策略

时间:2017-08-14 20:12:26

标签: algorithm game-theory

问题:
鉴于100颗宝石,两名球员交替拿出石头。人们可以从1到15取任何数字;但是,人们不能采取已经采取的任何数字。如果在游戏结束时,还剩下k个宝石,但是之前已经完成了1到k,一个人可以拿k石。拿最后一块石头的人获胜。第一个球员怎么能一直赢?

我的想法
使用递归(或动态编程)。基本情况1,其中玩家1具有获胜策略。 减少:对于剩下的n个宝石,如果palyer 1需要m1个宝石,他必须确保玩家​​2拥有(m2)的所有选项,他有一个获胜策略。因此问题减少到(n - m1 - m2)。

跟进问题:
如果使用DP,则要填充的表的潜在数量很大(2 ^ 15),因为剩余的可用选项取决于历史记录,其具有2 ^ 15种可能性。
你怎么优化?

2 个答案:

答案 0 :(得分:0)

假设剩下的数字组可以表示为R,选择后剩余的最大数字可以用RH表示,剩下的最小数字可以是RL,诀窍是使用倒数第二个移动到将数字提高到< 100-RH,但是> 100-RH-RL。这迫使你的对手拿出一个可以让你获胜的数字。

最终的获胜范围,以及您通过倒数第二步创建的总数,是:

N < 100-RH
N > 100-RH-RL

通过观察,我注意到RH可以高达15,低至8. RL可以低至1,高达13.从这个范围我评估方程。

N < 100-[8:15]         => N < [92:85]
N > 100-[8:15]-[1:13]  => N > [92:85] - [1:13] => N > [91:72]

其他考虑因素可以缩小这一差距。例如,RL在边缘情况下仅为13,总是导致玩家A的损失,因此真实范围在72到91之间.RH和它的低端存在类似的问题,因此最终范围和计算是:

N < 100-[9:15]         => N < [91:85]
N > 100-[9:15]-[1:12]  => N > [91:85] - [1:12] => N > [90:73]
[90:73] < N < [91:85]
然而,在此之前,可能性会爆发。请记住,这是在您选择倒数第二个数字之后,而不是之前。此时他们被迫选择一个允许你获胜的数字。

请注意,即使它可能存在,90也不是赢得胜利的有效选择。因此,它的最大值可以是89.N的实际范围是:

[88:73] < N < [90:85]
但是,有可能计算出你的对手处于不赢状态的数字范围。在你发现自己的情况下,最低的数字或最高的数字可能是你选择的那个,所以如果RHc是你可以选择的最高数字而RLc是你可以选择的最低数字,那么

RHc = [9:15]
RLc = [1:12]

有了这些信息,我可以从游戏结束开始构建一个相对算法。

N*p* - RHp - RLp < Np < N*p* - RHp, where p = iteration and *p* = iteration + 1
RHp = [8+p:15]
RLp = [1:13-p]
p = -1 is your winning move
p = 0 is your opponent's helpless move
p = 1 is your set-up move
Np is the sum of that round.

因此,为你的设置移动求解算法,p = 1,你得到:

N*p* - [9:15] - [1:12] < Np < N*p* - [9:15]
100 <= N*p* <= 114

我还在为此计算数学,所以期待调整。如果您发现错误,请告诉我,我会适当调整。

答案 1 :(得分:0)

这是一个简单的,强力的Python代码:

# stoneCount:    number of stones to start the game with
# possibleMoves: which numbers of stones may be removed? (*sorted* list of integers)
# return value: signals if winning can be forced by first player; 
#               if True, the winning move is attached
def isWinningPosition(stoneCount, possibleMoves):
    if stoneCount == 0:
        return False
    if len(possibleMoves) == 0:
        raise ValueError("no moves left")
    if stoneCount in possibleMoves or stoneCount < possibleMoves[0]:
        return True,stoneCount
    for move in possibleMoves:
        if move > stoneCount:
            break
        remainingMoves = [m for m in possibleMoves if m != move]
        winning = isWinningPosition(stoneCount - move, remainingMoves)
        if winning == False:
            return True,move
    return False

对于给定的问题大小,此函数在英特尔i7上的返回时间不到20秒:

>>> isWinningPosition(100, range(1,16))
False

(所以第一场比赛不能在这种情况下取​​胜。无论他做出什么动作,都会为第二名球员赢得胜利。)

当然,运行时优化有很大空间。在上述实施中,达到了许多情况并且一次又一次地重新计算(例如,当第一场比赛拿下一块石头而第二场比赛拿下两块石头时,这将使第一个球员处于与每个球员所取得的石头数量相同的情况时相反)。因此,第一个(主要)改进是记住已计算的情况。然后,人们可以寻求更有效的数据结构(例如,将可能的移动列表编码为位模式)。