博弈论Nim取子问题,困扰千年的问题一行代码解决

简介: 云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 这个博弈问题非常古老,延续长度千年之久,一直到20世纪初才被哈佛大学的一个数学家找到解法,可见其思维的难度。

云栖号资讯:【点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


这个博弈问题非常古老,延续长度千年之久,一直到20世纪初才被哈佛大学的一个数学家找到解法,可见其思维的难度。但是这个问题本身却很有意思,推导的过程更是有趣,哪怕你没有多少数据基础也一定可以看明白。

Nim取子问题

这个问题的题面是这样的,我们有3堆石子,有A和B两个人轮流从其中的一堆取石子。规定每个人每次最少取1颗,最多可以取完当前堆,无法继续拿取石子的人落败。请问如果你是先手,你有必胜策略吗?

根据我们之前分析威佐夫博弈问题的套路,我们需要先来分析一下问题,找到一些典型的局面。比如说(0, 0, 0)对于先手来说一定是必败的,同理,对于一个(0, n, n)的局面,也一样是必败的。因为不论先手怎么取石子,后手只需要在另外一堆石子当中如法炮制,那么留给先手的依然是一个(0, n, n)的局面。在博弈论问题当中,我们通常会将先手必败的局面称为奇异局势。

那么这些奇异局势之间有没有什么关联呢?我们能不能找到这些局面之间的联系或者是公式呢?

我们光是靠脑子想或者是用纸笔去罗列我们所能想到的奇异局面是很难想出来的,不然也不会困扰人们长达一千多年了。但是这个问题的谜底却又如此简单,简单到让人不可思议。

首先,我们先来思考一个问题,这个问题之所以复杂,根本原因在于石子有3堆,而不是两堆。如果石子有两堆,那么就很容易了,先手除非面临两堆石子相等的情况,否则必胜。因为它可以通过拿取石子留下两堆一样的给后手,这样不论后手如何拿取,先手只需要在另一堆当中采取同样的操作,就必然可以给后手留下奇异局势。这和我们刚才分析的(0, n, n)的局面是一样的。

但是题目明确说了是3堆而不是两堆,我们不禁就开始设想起了一个问题,我们能不能想到一种策略,使得可以将三堆石子”转化“或者是看成是两堆石子呢?这样我们就可以非常容易地判断石子的输赢情况了。

解法分析

明明是3堆石子,怎么看成是两堆呢?怎么看都是自说自话,但如果你对二进制熟悉的话,你会发现这个问题可能并不是不可能的。

是的,二进制就是天生的二维“生物”,在二进制的世界当中,一切都只有两种,0和1。所以从直观上我们会觉得,也许可以将石子的数量和二进制取得关联。也许这样的关联会有助于我们找到解法。剩下的问题就成了,这个关联究竟是什么?

我们来思考另外一个问题,对于一堆石子来说,我们取走一个数量,石子的数量会减少,这是显而易见的。体现在石子的总数上,就是表示这堆石子数量的数字,减去了另外一个数字。这个是减法的操作,小学生都知道。但是小学生不知道的是,减法在二进制当中是怎么进行的,或者是它有什么规律呢?

我们先不急着回答,先来仔细分析一波。首先,减数和被减数都可以化作是二进制,也就是若干个1和0组成的数字。我们假设减数每一个为1的二进制位对应的被减数的值也是1,那么这个减法会进行得非常顺利。对应的就是从被减数当中移除掉若干个1的过程。

举个例子,被减数是9,减数是1。我们都知道9写成二进制是1001,而1的二进制是1。所以被减数减去减数的值为8,也就是1000,可以看成是1001移除了末尾的1。

如果减数存在二进制位被减数为0,比如10 - 3的情况,10的二进制是1010,3是11。很明显3的第0位是1,而10是0,这种情况下怎么办?首先,我们先把3和10当中都是1的二进制位去除。剩下的就是1000 减去 1,那么我们可以先把1000 减1 变成111,这样就回到了上面说的第一种情况,完成减法之后再加回来,所以得到的结果就是111,这其实就是一个向高位借位的过程。纵观整个减法的计算过程,其实就是被减数当中二进制位变化的过程,减去某一个数,等价于将被减数当中若干个0变成1,1变成0。

结合二进制,我们可以想到一种策略。就是统计这3个数所有的二进制位,由于我们有3个数,所以每一个二进制位最多有3个1,最少有0个1。如果每一位的1的数量和都是偶数,也就是不是0就是2的话,那么这一定是一个奇异局面。

举个例子,比如[10, 8, 2]是一个奇异局面,我们把它们写成二进制。10的二进制是1010,8的二进制是1000,2的二进制是10。所以我们可以发现这三个数的二进制位加起来,第1、2、3位都出现了两个1。这个时候先手不论如何操作,后手只需要保证剩下的三个数的二进制位维持这个特性即可。这样做可以保证最后一次拿取结束之后,给先手留下[0, 0, 0]的局面。本质上来说,它的原理和两堆石子的时候是一样的,只不过转化了一种形式。

举个例子,比如我们从10当中拿走3颗石子,得到(7, 8, 2),我们观察二进制位分别是111, 1000, 10。会发现每一位1的数量从低到高分别是[1, 2, 1, 1]。所以我们可以从1000拿取3个石子,保证留下的数量是101,也就是5。这样剩下的1的个数就是[2, 2, 2],依然是偶数。所以先手不论如何拿,后手都可以保证一定可以让留下的数字在二进制上保持偶数,先手一定必败。在不满足这个条件的局面当中先手一定必胜,因为先手可以在第一次通过拿取掉多余的1,保证留下一个必败的局面给后手。

这也是这题的解法,即通过二进制位来判断是否先手必胜。我们要判断每个二进制位当中出现的1的次数和是否是偶数,可以通过位运算的亦或来完成。在亦或操作当中,对每一个二进制位进行计算,奇数为1,偶数为0。所以我们只需要计算一下这三堆石子亦或之后的结果是否为0,就可以知道是否每一个二进制位的1的数量是否都是偶数了。

我们写成代码非常简单,我们通常用^这个符号表示亦或运算,那么代码只需要一行:

def win_or_lose(a, b, c):
    return (a ^ b ^ c) == 0

推广以及证明

这里还没有结束,我们同样可以将3堆石子的局面推广到n堆,不管游戏当中玩家面临的是多少堆石子,这个结论依然都是成立的。这个成立的原因我们很容易想明白,为了严谨起见,我们可以用博弈问题常用的证明套路来证明一下。

在一个博弈问题当中,如果存在奇异局面,也就是必败局面,那么一定满足三个条件。第一个条件是无法进行任何操作的局面是奇异局面。第二个条件是可以移动到奇异局面的局面是非奇异局面。第三个条件是在奇异局面当中所作的任何操作得到的都是非奇异局面。

只要能够证明这三点,就可以证明我们的思路是正确的。

对于第一点毋庸置疑,所有石堆都没有石子的时候无法移动,这是必败状态。

我们来看第二个条件,我们假设这n堆石子的数量是a1, a2, ... an。如果当前局面是非奇异局面,根据我们的理论,那么a1 ^ a2 ^ a3 ^... ^an > 0。也就是说存在某个二进制位1的数量是奇数。

我们假设a1 ^ a2 ^ a3 ^... ^an = k,那么必然可以找到一个ai, 使得它的二进制表示在k的最高位上是1,因为k的所有二进制的1都是从这n个数当中来的,所以这样的ai一定存在。那么我们可以继续推导得到:ai ^ k < ai。因为最高位的1经过亦或之后变成了0,所以亦或操作之后一定是减小的。我们令p = ai ^ k,我们在a1^a2^a3^...^an = k 的等式两边同时亦或ai,可以得到a1 ^ a2 ^ ...^ai-1^ai+1^...^an = k ^ai,所以a1 ^ a2 ^ ...^ p ^...an = 0。

第三个条件也很好证明,因为如果当前是必败局面,也就是说a1 ^ a2 ^ ... ^ an=0。我们假设我们将an转变成了p之后依然有a1 ^ a2 ^ ... ^p=0, p < an。我们在等式两边同时亦或上p和an,可以得到:an ^ p = 0,也就是说p = an。这与p < an矛盾,所以不存在这样的转化使得奇异局面操作之后仍然是奇异局面。

这样我们就从数学上证明了这个推理的正确性,实际上已经有人对Nim取子问题有过深入的研究,这也是一个已经得到过证明的定理,叫做Bouton定理。定理的内容是先手可以在非平衡的Nim博弈中取胜,而后手可以在平衡的Nim博弈中取胜。这里的平衡就是指的是所有二进制位1的数量是偶数。

那么我们写出代码也非常简单:

def win_or_lose(nums):
    ret = 0
    for i in nums:
        ret ^= i
    return ret == 0

总结

到这里,关于Nim博弈的问题就讲完了。通过亦或操作去判断的解法真的是非常简单,但是这其中的推导过程想明白却不容易。我看过很多博客,都是直接给出的亦或这个结论,很少能够看到详细的推导过程。直接记住结论是简单的,但也很容易忘记,只有亲自推导一遍,才会明白亦或这个神奇的操作是怎么来的,为什么它可以解决Nim博弈的问题。

在整个思考推理和证明的过程当中,我们大量使用了亦或这个位运算操作,如果对它不熟悉的同学可能会看起来有些困扰。建议可以先了解学习一下二进制当中亦或的性质之后再来阅读本文,效果会更好。

目前为止,我们已经介绍完了巴什博奕、威佐夫博弈和Nim博弈这三种相对比较简单的博弈模型。在后续的文章当中,我们将会继续深入博弈论这个问题,一起去研究更加困难的博弈论问题,看看在复杂的场景当中,我们怎么样寻找奇异状态。

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-06-30
本文作者:承志
本文来自:“掘金”,了解相关信息可以关注“掘金”

相关文章
|
6月前
|
人工智能 决策智能
博弈论:Nim游戏
博弈论:Nim游戏
|
3月前
|
并行计算 算法 数据处理
编程之道:从代码中领悟技术与生活的哲理
【8月更文挑战第28天】在数字世界的迷宫中,每一行代码都像是宇宙中的一个星系,既独立又相互联系。本文将通过一段简单的Python代码示例,探讨如何从编程实践中汲取生活智慧。我们将看到,代码不仅仅是冷冰冰的指令序列,它也能反映出人类思维的深度和广度。正如甘地所言:“你必须成为你希望在世界上看到的改变。”在编程的世界里,我们同样可以创造并见证这种改变。
43 3
|
7天前
|
Python
探索编程之道:从代码中寻找生活的哲理
【10月更文挑战第35天】在编程的世界里,每一行代码都蕴含着深刻的意义。就像生活中的每一个选择都会影响我们的未来一样,代码中的每个决策也会塑造程序的运行结果。本文将通过一个简单的代码示例,探讨如何从编程中汲取生活的智慧,以及如何在面对技术挑战时保持初心和持续学习的态度。让我们一起走进编程的世界,发现那些隐藏在代码背后的生活哲理吧!
|
5月前
|
程序员 C++ Windows
【C++航海王:追寻罗杰的编程之路】探寻实用的调试技巧
【C++航海王:追寻罗杰的编程之路】探寻实用的调试技巧
38 0
|
6月前
|
算法 安全 程序员
代码之韵:寻找编程中的诗意
【5月更文挑战第11天】 在数字的严谨与逻辑的框架之下,编程往往被视为一门枯燥的技艺。然而,随着技术的不断深入与个人实践的积累,我开始领悟到编程不仅仅是冷冰冰的指令序列,它如同一种现代的文学,蕴含着独特的美学和节奏感。本文将探讨如何在编程的过程中找到那些令人着迷的“诗行”,并分享一些提升代码“艺术性”的个人感悟。
|
6月前
leetcode:292. Nim 游戏(数学推理)
leetcode:292. Nim 游戏(数学推理)
35 0
|
6月前
|
人工智能 安全 机器人
OpenAI换血大震动始末:“ChatGPT之父”奥特曼,缘何被“扫地出门”?
近期,AI业界发生了一场“大地震”。作为聊天机器人ChatGPT的开发者,OpenAI无疑是最受关注的人工智能公司之一。就是这家公司的联合创始人、CEO、有“ChatGPT之父”之称的阿尔特曼在“疯狂的5天”里,经历了被闪电免职、加入微软、最终又官复原职的戏剧性反转。 ChatGPT:我是ChatGPT,一个由OpenAI训练的大型语言模型,我能够理解和生成文本,并回答各种问题。 作为公司CEO的阿尔特曼被视为ChatGPT的代言人,穿梭于世界各地,与政、商及科技界领袖会面,他即将完成一笔融资,对OpenAI的估值将接近900亿美元。 AI(人工智能)新锐巨头OpenAI的最大股东兼合作
|
机器学习/深度学习 决策智能
博弈论-nim 游戏
博弈论-nim 游戏
158 0
博弈论-nim 游戏
|
决策智能
SG函数Nim游戏博弈论
SG函数Nim游戏博弈论
83 0
SG函数Nim游戏博弈论
|
决策智能
博弈论第十八集总结(“最后通牒和讨价还价”的观后感)
博弈论第十八集总结(“最后通牒和讨价还价”的观后感)
233 0