本文始发于个人公众号:TechFlow,原创不易,求个关注

今天是算法与数据结构专题26篇文章,我们来看看一个新的博弈论模型——Nim取子问题。

这个博弈问题非常古老,延续长度千年之久,一直到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博弈这三种相对比较简单的博弈模型。在后续的文章当中,我们将会继续深入博弈论这个问题,一起去研究更加困难的博弈论问题,看看在复杂的场景当中,我们怎么样寻找奇异状态。

文章就到这里,如果喜欢本文,可以的话,请点个关注,给我一点鼓励,也方便获取更多文章。

本文使用 mdnice 排版

博弈论Nim取子问题,困扰千年的问题一行代码解决的更多相关文章

  1. dede取子栏目时重复显示同级栏目的终极解决方法

    使用channelartlist标签时,当栏目没有子栏目是,会出现重复同级栏目的问题,解决方法如下: 先看下面的代码{dede:channelartlist typeid='2'}  {dede:ty ...

  2. 萌新笔记之Nim取石子游戏

    以下笔记摘自计算机丛书组合数学,机械工业出版社. Nim取石子游戏 Nim(来自德语Nimm!,意为拿取)取石子游戏. 前言: 哇咔咔,让我们来追寻娱乐数学的组合数学起源! 游戏内容: 有两个玩家面对 ...

  3. jquery 取子节点及当前节点属性值

    分享下jquery取子节点及当前节点属性值的方法. <li class="menulink"><a href="#" rel="ex ...

  4. phpcms直接取子栏目的内容、调用点击量的方法

    子栏目里面的内容可以直接取,而不需要通过循环. {$CATEGORYS[$catid][catname]}//取子栏目的栏目名称 {$CATEGORYS[$catid][image]}//取子栏目的栏 ...

  5. 51nod1069【Nim取石子游戏】

    具体看:萌新笔记之Nim取石子游戏可以这么写: #include <bits/stdc++.h> using namespace std; typedef long long LL; in ...

  6. BZOJ.1299.[LLH邀请赛]巧克力棒(博弈论 Nim)

    题目链接 \(Description\) 两人轮流走,每次可以从盒子(容量给定)中取出任意堆石子加入Nim游戏,或是拿走任意一堆中正整数个石子.无法操作的人输.10组数据. \(Solution\) ...

  7. 使用Python爬取淘宝两千款套套

    各位同学们,好久没写原创技术文章了,最近有些忙,所以进度很慢,给大家道个歉. 警告:本教程仅用作学习交流,请勿用作商业盈利,违者后果自负!如本文有侵犯任何组织集团公司的隐私或利益,请告知联系猪哥删除! ...

  8. JAVA中取子字符串的几种方式

    有这样一串字符串:String s = "共 100 页, 1 2 3 4..."; 假如我想把"100"给取出来,该如何做? 方法一: 采用split的方式 ...

  9. Jquery-获取子元素children,find

    1.查找子元素方式1:> 例如:var aNods = $("ul > a");查找ul下的所有a标签 2.查找子元素方式2:children() 3.查找子元素方式3 ...

  10. HDU 2516 (Fabonacci Nim) 取石子游戏

    这道题的结论就是,石子的个数为斐波那契数列某一项的时候,先手必败:否则,先手必胜. 结论很简单,但是证明却不是特别容易.找了好几篇博客,发现不一样的也就两篇,但是这两篇给的证明感觉证得不清不楚的,没看 ...

随机推荐

  1. Li-Fi,LED光无线局域网

    无需WiFi信号,点一盏LED灯就能上网.昨天,复旦大学计算机科学技术学院传出好消 息,一种利用屋内可见光传输网络信号的国际前沿通讯技术在实验室成功实现.研究人员将网络信号接入一盏1W的LED灯珠,灯 ...

  2. 使用Ant命令压缩JavaScript文件

    压缩JavaScript文件可以减少代码尺寸,保护源代码,节省网络带宽,加快页面打开速度,甚至优化JS代码.Yahoo有一个压缩JS的工具叫做YUI compressor, Google也有一个工具叫 ...

  3. 20150528&mdash;html使用Jquery遍历text文本框的非空验证

    <script src="jquery-1.7.2.min.js" type="text/javascript"></script> & ...

  4. [转]DataTable用中使用Compute 实现简单的DataTable数据的统计

    本文转自:http://blog.csdn.net/zwxrain/article/details/252285 調用格式: object DataTable.Compute(string expre ...

  5. api1

    http://www.android-doc.com/reference/android/app/Fragment.html

  6. struts2的配置和使用

    一:配置stuts2的运行环境,把以下的包放到WEB-INF/lib目录下 1,structs2-core-2.1.6.jar       struts的核心库 2,xwork-2.1.2.jar   ...

  7. linkin大话面向对象--包装类

    Java提倡的万物皆对象,但是数据类型的划分出现了基本数据类型和引用数据类型,那么我们怎么能把基本数据类型称为对象呢? 基本数据类型 包装类 byte Byte short Short int Int ...

  8. 服务器资源迁移到aliyun对象存储及oss的权限管理配置

    chinasoft-download增值服务的迁移和部署 需求: 增值服务网站需要从网宿迁移到阿里云,以前的增值服务历史软件存放在服务器中需要迁移到阿里云的oss中存放 需要改造程序给程序添加一个os ...

  9. 安卓学习第一节--环境搭建及Android Studio 安装

    1.安装JDK 2.安装AS 安装参考网址 https://www.cnblogs.com/xiadewang/p/7820377.html 下载网址: http://www.android-stud ...

  10. [转载]Oracle Golden Gate - 概念和机制 (ogg)

    出处:https://www.cnblogs.com/qiumingcheng/p/5435907.html Golden Gate(简称OGG)提供异构环境下交易数据的实时捕捉.变换.投递. OGG ...