【趣题】几堆石子轮流捡,谁捡到最后的石子算输的游戏

一 题目描述

有三堆石子,分别为7,5,3个每堆,两个人轮流进行如下操作:

    选择一堆(石子数不为0);

    从这堆石子中取走至少一个,至多全部的石子;

直到有人拿走最后一颗石子,该人算输。

二 思路

当时直觉上这样的题目先手必胜概率很大,但今天才有机会用代码写了一下。

1. 这种棋子数量不多的情况下,完全可以采用暴力求解的手段。

2. 如果基于目前局势,能够导致对手必输的局势,那么目前选手必胜;否则,目前选手必败。这个非黑即白的逻辑解释如下:

    我们可以从最终的情况逐步往上添加石子构建到初始情况。比如1-0-0的情况是先手必败情况,那所有一次操作能够形成1-0-0的局势都认为是先手必胜;如果某一个局势不能通过减少石子变为任何一个先手必败的情况,那它只能是通过先手必胜的局势添加石子构建而来,换句话说,无论这个局势下怎么动,都是留给对手一个先手必胜的局势,这种情况下,先手必败。

3. 通过局势生成局势码,避免重复计算,进行剪枝。(实际效果:将递归调用次数从98708次降低到5592次)

三 代码

 
 
 
 
# -*- coding:UTF-8 -*-
cnt = 0 # 计数器,看调用函数的次数
deadSet = set() # 因为涉及一些重复判断,所以使用一个set记录必输的情况,减少迭代次数
def genDeadNum(curList): # 局势转化为局势码
    return "-".join([str(x) for x in sorted(curList, reverse = True)])
def decide(a):
    global cnt
    aOld = a[:] # 保存我们的局势 以及 我们操作之后的局势
    for pileIdx in xrange(0, len(a)): # 选取一堆石子
        if a[pileIdx] == 0: # 如果本身没有石子,略过
            continue
        else:
            temp = a[pileIdx] # 备份原始数量
            for remainNums in xrange(0,  temp): # 选择拿走之后剩余的数量
                print pileIdx, remainNums
                a[pileIdx] = remainNums
                if sum(a) == 0: # 如果都拿没了,说明我输了,我们先略过
                    continue
                else: # 还有石子,看对手的处境了
                    testNum = genDeadNum(a) # 我们先生成对应局势的码
                    if testNum in deadSet or decide(a) == 0: # 如果对应局势是必输,或者我们迭代后发现结果为0,对手必输,我们必胜,返回 1
                        print "From", aOld, "to ", a
                        a[pileIdx] = temp
                        cnt += 1
                        return 1 # 返回必胜的代码 1
            a[pileIdx] = temp # 遍历完取这堆石子的可能性,记得还原这堆石子的初始状况
    print "When the piles like ", a, "I cannot win!" # 如果上面的所有遍历都无法找到必胜手段,那就说明我们必然输了。
    deadNum = genDeadNum(a) # 生成必将失败的局势码加到死亡集合中
    deadSet.add(deadNum)
    cnt += 1
    return 0 # 必输返回 0

if __name__ == "__main__":
    a= [7, 5, 3]
    res = decide(a)
    print res, cnt
    print list(deadSet)
 
 

可以看到,最终先手必败的情形只有如下几种

'3-2-1', '6-4-2', '1-0-0', '1-1-1', '3-3-0', '5-5-0', '2-2-0', '6-5-3', '4-4-0', '5-4-1'

比如:我们第一步是将7-5-3转化为6-5-3,给对手一个必败局势,然后大家可以自行想象未来的情形。

四 延伸, 谁捡到最后一个谁赢的游戏

修改规则变为谁最后一个拿谁赢

那么就变为异或的问题了。假设两种情形:

1. 三堆石子的数量异或之后为0

2. 三堆石子的数量异或之后为非0

对情形1进行任意取石子,都导致剩余的石子转换为情形2

对情形2的情况,必然可以用一种取石子方法将剩余石子转换为情形1

最后胜利的人是将情形转换为情形1的那个(0-0-0)


这样,如果初始为情形2,那么先手的人,只要负责每次操作将情形2转换为情形1即可。

如果初始为情形1,那么先手的人被迫给后手一个情形2,后手通过上句策略可胜。

代码稍作修改即可,略。

代码给出的必输情形如下,也就是所有可能的情形1的罗列。

['3-2-1', '6-4-2', '1-1-0', '5-4-1', '5-5-0', '2-2-0', '6-5-3', '4-4-0', '3-3-0']







猜你喜欢

转载自blog.csdn.net/GreatFeihao/article/details/80036876