第1章 游戏之乐——NIM(2)“拈”游戏分析

NIM(2)“拈”游戏分析

1. 问题

  有N块石头和两个玩家A和B,玩家A先将石头分成若干堆,然后按照BABA……的顺序不断轮流取石头,能将剩下的石头一次取光的玩家获胜。每次取石头时,每个玩家只能从若干堆石头中任选一堆,取这一堆石头中任意数目(大于1)个石头。

请问:玩家A有必胜策略吗?要怎么分配和取石头才能保证自己有把握取胜?

2. 解法与分析

据说,该游戏起源于中国,英文名字叫做“NIM”,是由广东话“拈”(取物之意)音译而来,经由当年到美洲打工的华人流传出去,这个游戏一个常见的变种是将十二枚硬币分三列排成 [3,4,5] 再开始玩 。我们这里讨论的是一般意义上的“拈”游戏。

言归正传,在面试者咄咄逼人的目光下,你要如何着手解决这个问题?

在面试中,面试者考察的重点不是“what”——能否记住某道题目的解法,某件历史事件发生的确切年代,C++语言中关于类的继承的某个规则的分支等。面试者很想知道的是“how”——应聘者是如何思考和学习的。

所以,应聘者得展现自己的思路。解答这类问题应从最基本的特例开始分析。我们用N表示石头的堆数,M表示总的石头数目。

当N=1时,即只有一堆石头——显然无论你放多少石头,你的对手都能一次全拿光,你不能这样摆。

当N=2时,即有两堆石头,最简单的情况是每堆石头中各有一个石子(1,1)——先让对手拿,无论怎样你都可以获胜。我们把这种在双方理性走法下,你一定能够赢的局面叫作安全局面。

当N = 2,M > 2时,既然(1, 1)是安全局面,那么(1, X)都不是安全局面,因为对手只要经过一次转换,就能把(1, X)变成(1, 1),然后该你走,你就输了。既然(1, X)不安全,那么(2, 2)如何?经过分析,(2,2)是安全的,因为它不能一步变成(1,1)这样的安全局面。这样我们似乎可以推理(3, 3)、(4, 4),一直到(X, X)都是安全局面。

于是我们初步总结,如果石头的数目是偶数,就把它们分为两堆,每堆有同样多的数目。这样无论对手如何取,你只要保证你取之后是安全局面(X, X),你就能赢。

好,如果石头数目是奇数个呢?

当M=3的时候,有两种情况,(2, 1)、(1, 1, 1),这两种情况都会是先拿者赢。

当M=5的时候,和M=3类似。无论你怎么摆,都会是先拿者赢。

若M=7呢?情况多起来了,头有些晕了,好像也是先拿者赢。

我们在这里得到一个很重要的阶段性结论:

当摆放方法为(1, 1,…, 1)的时候,如果1的个数是奇数个,则先拿者赢;如果1的个数是偶数个,则先拿者必输。

当摆放方法为(1, 1,…, 1, X)(多个1,加上一个大于1的X)的时候,先拿者必赢。因为:

如果1有奇数个,先拿者可以从(X)这一堆中一次拿走X-1个,剩下偶数个1——接下来动手的人必输。

如果有偶数个1,加上一个X,先拿者可以一次把X都拿光,剩下偶数个1——接下来动手的人也必输。

当然,游戏是两 个人玩的,还有其他的各种摆法,例如当M = 9的时候,我们可以摆为(2, 3, 4)、(1, 4, 4)、(1, 2, 6),等等,这么多堆石头,它们既互相独立,又互相牵制,那如何分析得出致胜策略呢?关键是找到在这一系列变化过程中有没有一个特性始终决定着输赢。这个 时候,就得考验一下真功夫了,我们要想想大学一年级数理逻辑课上学的异或(XOR)运算。异或运算规则如下:

XOR(0, 0)= 0

XOR(1, 0)= 1

XOR(1, 1)= 0

首先我们看整个游戏过程,我们从N堆石头(M1, M2, …, Mn)开始,双方斗智斗勇,石头一直递减到全部为零(0, 0,…, 0)。

当M为偶数的时候,我们的取胜策略是把M分成相同的两份,这样就能取胜。

开始:(M1, M1)      它们异或的结果是XOR(M1, M1)= 0

中途:(M1, M2)      对手无论怎样从这堆石头中取,XOR(M1, M2)!= 0

我方:(M2, M2)      我方还是把两堆变相等。XOR(M2, M2)= 0

最后:(M2, M2)      我方取胜

类似的,若M为奇数,我们把石头分成(1, 1, …,1)奇数堆的时候,XOR(1, 1,…,1)[奇数个] !=0。而这时候,对方可以取走一整堆,XOR(1, 1,…, 1)[偶数个]=0,如此下去,我方必输。

我们推广到M为奇数,但是每堆石头的数目不限于1的情况,看看XOR值的规律:

开始:(M1, M2, …, Mn)           XOR(M1, M2, … Mn)=?

中途:(M1’, M2’, … Mn’)        XOR(M1’, M2’, … Mn’)=?

最后:(0, 0, …, 0)                   XOR(0,0,… 0)=0

不幸的是,可以看出,当有奇数个石头时,无论你如何分堆,XOR(M1, M2, … Mn) 总是不等于0!因为必然会有奇数堆有奇数个石头(二进制表示最低位为1),异或的结果最低位肯定为1。                                                                                            [结论1]

再不幸的是,还可以证明,当XOR(M1, M2, … Mn)!= 0时,我们总是只需要改变一个Mi的值,就可以让XOR(M1, M2, …Mi’,… Mn)= 0。                        [结论2]

更不幸的是,又可以证明,当XOR(M1, M2, … Mn)= 0时,对任何一个M值的改变(取走石头),都会让XOR(M1, M2, …Mi’,… Mn)! = 0。                      [结论3]

有了这三个“不幸”的结论,我们不得不承认,当M为奇数时,无论怎样分堆,总是先动手的人赢。

还不信?那我们试试看:当M=9,随机分堆为(1,2,6)

开始:(1,2,6)

1=0 0 1
       2=0 1 0
       6=1 1 0
    XOR=1 0 1            即XOR(1,2,6)!=0

B先手:(1, 2, 3),即从第三堆取走三个,得到(1,2,3)

1=0 0 1
       2=0 1 0
       3=0 1 1
    XOR=0 0 0            所以,XOR(1,2,3)=0

A方:(1, 2, 2)XOR(1, 2, 2)!=0。

B方:(0, 2, 2)XOR (0, 2, 2)=0

……A方继续顽抗……

B方最后:(0, 0, 0),XOR(0, 0, 0)= 0

好了,通过以上的分析,我们不但知道了这类问题的答案,还知道了游戏的规律,以及如何才能赢。XOR,这个我们很早就学过的运算,在这里帮了大忙。我们应该对XOR说Orz才对!

有兴趣的读者可以写一个程序,返回当输入为(M1, M2, …, Mn)的时候,到底如何取石头,才能有赢的可能。比如,当输入为(3, 4, 5)的时候,程序返回(1, 4, 5)——这样就转败为胜了!程序如下:

 1 package chapter1youxizhileNIM2;
 2 /**
 3  * NIM(2)"拈"游戏分析
 4  * @author DELL
 5  *
 6  */
 7 public class NIM2 {
 8     private int[] a;  //分堆数组
 9     //构造方法
10     public NIM2(int[] a){
11         this.a = a;
12     }
13     //获取取石头的方法
14     public void getResult(){
15         int temp;  //临时存储计算结果
16         int sum = a[0]; //存储所有数组元素的异或结果
17         int i,j;
18         System.out.print("原始分堆情况:(");
19         for(i=0;i<a.length-1;i++){
20             System.out.print(a[i]+",");
21         }
22         System.out.println(a[a.length-1]+")");
23         for(j=1;j<a.length;j++){
24             sum ^= a[j];
25         }
26         for(i=0;i<a.length;i++){
27             temp = sum^a[i];
28             if(temp<a[i]&&temp>=0){
29                 a[i] = temp;
30                 break;
31             }
32         }
33         if(i>=a.length)
34             System.out.println("怎样取都无法取胜!");
35         else{
36             System.out.print("取完后的分堆情况:(");
37             for(i=0;i<a.length-1;i++){
38                 System.out.print(a[i]+",");
39             }
40             System.out.println(a[a.length-1]+")");
41         }
42     }
43     public static void main(String[] args) {
44         int[] a = {3,4,5};
45         NIM2 nima = new NIM2(a);
46         nima.getResult();
47         int[] b = {1,2,6};
48         NIM2 nimb = new NIM2(b);
49         nimb.getResult();
50     }
51
52 }

该程序的时间复杂度为O(N),N为堆的个数。

程序运行结果如下:

原始分堆情况:(3,4,5)
取完后的分堆情况:(1,4,5)
原始分堆情况:(1,2,6)
取完后的分堆情况:(1,2,3)

3. 扩展问题

1.如果规定相反,取光所有石头的人输,又该如何控制局面?

分析方法同上。

2.如果每次可以挑选任意K堆,并从中任意取石头,又该如何找到必胜策略呢?

分析方法同第1章 游戏之乐——NIM(1)一排石子的游戏扩展问题。

时间: 2024-08-07 00:18:33

第1章 游戏之乐——NIM(2)“拈”游戏分析的相关文章

第1章 游戏之乐——一排石子的游戏

一排石子的游戏 转载:编程之美-MIN(1)一排石头的游戏 1. 原题 1.1 题目 N块石头排成一行,每块石头有各自固定的位置.两个玩家依次取石头,每个玩家每次可以取其中任意一块石头,或者相邻的两块石头,石头在游戏过程中不能移位(即编号不会改变),最后能将剩下的石头一次取光的玩家获胜.这个游戏有必胜策略吗? 1.2 解答 已知:石头数量为N,假设两个玩家分别为玩家A和玩家B,且玩家A先取石头. 当N<=2时,玩家A可以直接取完所有的石头,玩家A有必胜策略. 当N=3时,玩家A先取中间的1个石头

nim(2)&quot;拈&quot;游戏分析

来自<编程之美>上的博弈问题. 题目是这样的.有M块石头和两个玩家A和B,玩家A先将石头分成若干堆,然后按照BABA.....的顺序不断轮流取石头,能将剩下的石头一次取光的玩家获胜.每次取石头时,每个玩家只能从若干堆石头任选一堆,取这一堆石头中任意数目(大于0)个石头.请问:玩家A要怎样分配和取石头才能保证自己有把握取胜? 分析与思考:我们可以将这个问题从最简单的情况逐渐复杂化分析,然后从中找出规律,然后再对所得规律加以证明.这样一个过程是分析问题解决问题的一种方式方法. 设N块石头堆,M块石

编程之美笔记--第一章游戏之乐--1.2中国象棋将帅问题

后来一版作者又将最后一句改为:”要求在代码中只能使用一个字节存储变量“. 我的解法: package android.zlb.java; /** * * @author zhanglibin * */ public class TestXiangqi { public static void main(String[] args) { for(int i = 11; i < 100; i++) { if(i / 10 % 3 == 1 && (i % 10 == 1 || i % 1

Nim 游戏、SG 函数、游戏的和

Nim游戏 Nim游戏定义 Nim游戏是组合游戏(Combinatorial Games)的一种,准确来说,属于"Impartial Combinatorial Games"(以下简称ICG).满足以下条件的游戏是ICG(可能不太严谨):1.有两名选手:2.两名选手交替对游戏进行移动(move),每次一步,选手可以在(一般而言)有限的合法移动集合中任选一种进行移动:3.对于游戏的任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作.以前的任何操作.骰子的点数

BZOJ 1874 取石子游戏 (NIM游戏)

题解:简单的NIM游戏,直接计算SG函数,至于找先手策略则按字典序异或掉,去除石子后再异或判断,若可行则直接输出. #include const int N=1005; int SG[N],b[N],hash[N],a[N],sum,tmp,i,j,n,m; void FSG(int s){ SG[0]=0; for(int i=1;i<=s;i++){ for(int j=1;b[j]<=i&&j<=m;j++)hash[SG[i-b[j]]]=i; for(int j

(linux shell)第二章--命令之乐(一)

文章来自于我的个人博客:(linux shell)第二章--命令之乐(一)    上一章我们描写叙述了一些linux shell中须要注意的一些语法.接下来我们開始了解linux shell的经常使用命令.let's go... cat 命令: cat本身表示拼接(concatenate).cat命令有一些经常使用參数,像-n,-s等,我们以下逐一介绍: 假设你想高速查看一个文本文件内容.就能够使用cat命令: cat file 假设你想一次查看多个文件内容.也是在后面加上文件路径就可以 cat

Unity 2D游戏开发快速入门第1章创建一个简单的2D游戏

Unity 2D游戏开发快速入门第1章创建一个简单的2D游戏 即使是现在,很多初学游戏开发的同学,在谈到Unity的时候,依然会认为Unity只能用于制作3D游戏的.实际上,Unity在2013年发布4.3版本的时候,就开始提供对制作2D游戏的支持了.例如,提供了一些专用于开发2D游戏的Unity工具.现在Unity已经发布了版本4.5,对2D游戏的支持更是完善了不少.为了说明Unity对2D游戏所提供的支持,本章会使用这些在Unity中原生的工具,开发一个简单的2D游戏.本文选自<Unity

HDU 4315(NIM游戏及其变种,组合游戏相关学习

题目:山上有n个人,每个人给出距离山顶的距离,给出其中一个人为king,每次能挑选一个人向上移动,不能越过其他人,最后将king移动到山顶者获胜.问获胜者. 思路:转化为NIM游戏. 简记: NIM游戏:有n堆石子,每次可以选择一堆拿走任意数量的石子,不能拿石子的一方失败. NIM游戏的必败态为所有堆的石子数目异或值为0的情况,那是因为,如果异或值不为0,设其为x,x的二进制表示中的最左边的一位1必然存在一堆(设为z)与之对应,将这一堆取成y=x^z,那么得到的状态为异或值为0(必败态),而必败

Dota 游戏中的攻击与伤害分析

摘要:在上一篇文章中分析了物理攻击和护甲的攻防分析,但是忽略了英雄对战里面一个很重要的角色--技能攻击.实际上,除了少数后期英雄可以直接靠平砍(即物理攻击)杀人外,大部分英雄尤其是智力英雄还是要靠技能收割人头的.技能的使用也是评价一个玩家水平高低的主要指标.在本文中,我们就技能进行分析. 关键字:技能攻击 魔抗 护甲 伤害类型 攻击类型 Dota中的攻击类型共有普通攻击.穿刺攻击.攻城攻击.混乱攻击.英雄攻击和法术攻击6种.除了法术攻击,其他的统称为物理攻击.然而我们只考虑英雄的话,只有英雄攻击