第1章 游戏之乐——NIM(3)两堆石头的游戏
NIM(3)两堆石头的游戏
1. 问题描述
假设有两堆石头,有两个玩家会根据如下的规则轮流取石头:每人每次可以从两堆石头中各取出数量相等的石头,或者仅从一堆石头中取出任意数量的石头;最后把剩下的石头一次拿光的人获胜。请问在哪些局面(依据两堆石头中的石头个数)下,先取石头的玩家有必胜的策略。
2. 解法
类似构造质数的筛选方法,这里我们利用找到的必输局面(后取的玩家有必胜策略)来筛去掉能通过一次操作达该必输局面的其它必胜局面(先取的玩家有必胜策略)。最后选出的局面都是必输局面。
构造必胜策略:
如果一开始的局面就是必输局面,那么可能先取的玩家没有必胜策略(当然如果后取的玩家不太聪明,先取的玩家依然有可能能赢)。如果一开始的局面不是必输局面,那么先取的玩家一定有必胜策略,且必胜策略就是保证每次都将当前非必输局面转变为必输局面(后取的玩家必输)。
先取者有必胜策略的局面为“安全局面”,而先取者无必胜策略的局面为“不安全局面”。通过筛选可得如下不安全局面表:
不安全局面表
N | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ... |
an | 1 | 3 | 4 | 6 | 8 | 9 | 11 | 12 | 14 | 16 | ... |
bn | 2 | 5 | 7 | 10 | 13 |
15 |
18 | 20 | 23 | 26 | ... |
有了通项公式,我们就能更加简明地实现函数bool nim(n,m),这个函数的时间复杂度为O(min(m,n))。代码如下:
package chapter1youxizhileNIM3;
/**
* 两堆石头的游戏
* @author DELL
*
*/
public class NIM3 {
/**
* 计算玩家是否能赢得游戏
* @param n 其中一堆石头的数量
* @param m 另一堆石头的数量
* @return 输赢状态
*/
public static boolean nim(int n, int m){
double a,b;
int temp;
a = (1+Math.sqrt(5.0))/2;
b = (3+Math.sqrt(5.0))/2;
if(m == n)
return true;
if(m < n){ //保证m>n
temp = m;
m = n;
n = temp;
}
int i = n;
while(i>=1){ //由于序号i值一定小于等于min(m,n)所以遍历查找
if((int)Math.floor(a*i)==n&&(int)Math.floor(b*i)==m)
return false;
i--;
}
return true;
}
public static void main(String[] args) {
if(nim(5,3))
System.out.println("(5,3)有必胜策略!");
else
System.out.println("(5,3)没有必胜策略!"); if(nim(3,6))
System.out.println("(3,6)有必胜策略!");
else
System.out.println("(3,6)没有必胜策略!");
} }
程序运行结果如下:
(5,3)没有必胜策略!
(3,6)有必胜策略!
3. 扩展问题
1. 现在我们已经给出了一个判断先取者是否能够最终赢得游戏的判断函数,但是,游戏的乐趣在于过程,大家能不能根据本题的思路给出一个赢得游戏的必胜策略呢?即根据当前石头的个数,给出当前玩家下一步要怎么取石头才能必胜。
程序如下:
package chapter1youxizhileNIM3;
/**
* 两堆石头的游戏
* @author DELL
*
*/
public class NIM3 {
/**
* 计算玩家是否能赢得游戏
* @param n 其中一堆石头的数量
* @param m 另一堆石头的数量
* @return 输赢状态
*/
public static boolean nim(int n, int m){
double a,b;
int temp;
a = (1+Math.sqrt(5.0))/2;
b = (3+Math.sqrt(5.0))/2;
if(m == n)
return true;
if(m < n){ //保证m>n
temp = m;
m = n;
n = temp;
}
int i = n;
while(i>=1){ //由于序号i值一定小于等于min(m,n)所以遍历查找
if((int)Math.floor(a*i)==n&&(int)Math.floor(b*i)==m)
return false;
i--;
}
return true;
} public static void result(int n,int m){
int min,max; //存储m,n中的最小值和最大值
int i;
if(m<=n){
min = m;
max = n;
}else{
min = n;
max = m;
}
if(m==n)
System.out.println("("+n+", "+m+")取后的状态为(0, 0)");
if(nim(n,m)){
for(i=1;i<=min;i++){
if(!nim(n-i,m-i)){
System.out.println("("+n+", "+m+")取后的状态为("+(n-i)+", "+(m-i)+")");
return;
}
if(!nim(n-i,m)){
System.out.println("("+n+", "+m+")取后的状态为("+(n-i)+", "+m+")");
return;
}
if(!nim(n,m-i)){
System.out.println("("+n+", "+m+")取后的状态为("+n+", "+(m-i)+")");
return;
}
}
for(i=min+1;i<=max;i++){
if(n>=m){
if(!nim(n-i,m)){
System.out.println("("+n+", "+m+")取后的状态为("+(n-i)+", "+m+")");
return;
}
}else{
if(!nim(n,m-i)){
System.out.println("("+n+", "+m+")取后的状态为("+n+", "+(m-i)+")");
return;
}
}
}
}else{
System.out.println("无论怎么取都没有必胜的策略!");
}
}
public static void main(String[] args) {
result(3,6);
result(6,6);
} }
程序运行结果如下:
(3, 6)取后的状态为(3, 5)
(6, 6)取后的状态为(0, 0)
2. 取石头的游戏已经不少了,但是我们还有一种游戏要请大家思考,我们姑且叫它NIM(4):
两个玩家,只有一堆石头,两人依次拿石头,最后拿光者为赢家。取石头的规则是:
(a)第一个玩家不能拿光所有的石头。
(b) 第一次拿石头之后,每人每次最多只能拿掉对方前一次所拿石头的两倍。
那么,这个游戏有没有必胜的算法?(提示:好像和Fibonacci数列有关。)
经分析可知,当这一堆石头的数量为Fibonacci值的时候,先拿者没有必胜策略。
判断的算法如下:
package chapter1youxizhileNIM3;
/**
* 扩展问题
* 两个玩家,只有一堆石头,两人依次拿石头,最后拿光者为赢家。取石头的规则是: (a)第一个玩家不能拿光所有的石头。 (b) 第一次拿石头之后,每人每次最多只能拿掉对方前一次所拿石头的两倍。 那么,这个游戏有没有必胜的算法?(提示:好像和Fibonacci数列有关。)
* @author DELL
*
*/
public class NIM4 {
/**
* 计算玩家是否能赢得游戏
* @param n 给定的一堆石头的数量
* @return 输赢状态
*/
public static boolean nim(int n){
int f1 = 1, f2 = 1; //Fibonacci数列的前两个值
int f3;
if(n==1)
return false;
do{
f3 = f1+f2;
f1 = f2;
f2 = f3;
}while(f3<n);
if(f3==n)
return false;
else
return true;
} public static void main(String[] args){
if(nim(3))
System.out.println("(3)有必胜策略!");
else
System.out.println("(3)没有必胜策略!");
if(nim(4))
System.out.println("(4)有必胜策略!");
else
System.out.println("(4)没有必胜策略!");
}
}
程序运行结果如下:
(3)没有必胜策略!
(4)有必胜策略!
第1章 游戏之乐——NIM(3)两堆石头的游戏的更多相关文章
- 第1章 游戏之乐——NIM(2)“拈”游戏分析
NIM(2)“拈”游戏分析 1. 问题 有N块石头和两个玩家A和B,玩家A先将石头分成若干堆,然后按照BABA……的顺序不断轮流取石头,能将剩下的石头一次取光的玩家获胜.每次取石头时,每个玩家只能从若 ...
- 第1章 游戏之乐——NIM(1)一排石子的游戏
NIM(1)一排石子的游戏 转载:编程之美-MIN(1)一排石头的游戏 1. 原题 1.1 题目 N块石头排成一行,每块石头有各自固定的位置.两个玩家依次取石头,每个玩家每次可以取其中任意一块石头,或 ...
- 编程之美 set 18 拈两堆石子游戏(3)
题目 假设有两堆石头, 有两个玩家按照如下规则轮流取石头 每个人每次可以从两堆石头中取出数量相等的石头, 或者仅从一堆石头中取出任意数量的石头 最后把剩下的石头依次拿光的人取胜 首先取石头的人能否赢得 ...
- Poj 1067 取石子游戏(NIM,威佐夫博奕)
一.Description 有两堆石子,数量任意,可以不同.游戏开始由两个人轮流取石子.游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子:二是可以在两堆中同时取走相同数量的石子. ...
- 【转】Unity3D研究院之两种方式播放游戏视频
http://www.xuanyusong.com/archives/1019 Unity3D中播放游戏视频的方式有两种,第一种是在游戏对象中播放,就好比在游戏世界中创建一个Plane面对象,摄像 ...
- NIM游戏,NIM游戏变形,威佐夫博弈以及巴什博奕总结
NIM游戏,NIM游戏变形,威佐夫博弈以及巴什博奕总结 经典NIM游戏: 一共有N堆石子,编号1..n,第i堆中有个a[i]个石子. 每一次操作Alice和Bob可以从任意一堆石子中取出任意数量的石子 ...
- (linux shell)第二章--命令之乐(一)
文章来自于我的个人博客:(linux shell)第二章--命令之乐(一) 上一章我们描写叙述了一些linux shell中须要注意的一些语法.接下来我们開始了解linux shell的经常使用 ...
- Python编写两个数的加减法游戏
目标: 1.实现两个数的加减法 2.回答者3次输错计算结果后,输出正确结果,并询问回答者是否继续 1.使用常规函数实现两个数的加减法游戏 代码如下: #!/usr/bin/env python # - ...
- NIM(1) 一排石头的游戏
最近在实习面试过程中,一个朋友遇到了该问题,从简单到复杂的思路如下,希望能给遇到相同问题的朋友一些启发和帮助.(内容来源网络和<编程之美>) 1.问题1 100个苹果 桌上有100个苹果, ...
随机推荐
- 《Python核心编程》 第八章 条件和循环
8–1.条件语句. 请看下边的代码 # statement A if x > 0: # statement B pass elif x < 0: # statement C pass el ...
- Linux基本命令(3)文件备份和压缩命令
文件备份和压缩命令 在Linux中,常用的文件压缩工具有gzip.bzip2.zip.bzip2是最理想的压缩工具,它提供了最大限度的压缩.zip兼容性好,Windows也支持. 命令 功能 bzip ...
- 数据库表中MAX ID获取,确保每次调用没有重复工具类(NumberUtil)
下面这个类是获取数据库中一个字段的最大值.配置在数据库中. public class NoFactory { private final static Logger cLogger = Logger. ...
- MFC字体与文本输出
字体 成员函数 1.CFont( ); 构造一个CFont对象.此对象在使用之前应该先使用CreateFont.CreateFontIndirect.CreatePointFont或CreatePoi ...
- Java相关书籍推荐
Java从入门到精通(第3版 附光盘) 作 者 明日科技 编 出 版 社 清华大学出版社 出版时间 2012-08-01 版 次 3 页 数 564 印刷时间 2012- ...
- Intellij IDEA,WebStorm-keymap(转)
1. ctrl + shift + n: 打开工程中的文件2. ctrl + j: 输出模板3. ctrl + b: 跳到变量申明处4. ctrl + alt + T: 围绕包裹代码(包括zencod ...
- 高精度+搜索+质数 BZOJ1225 [HNOI2001] 求正整数
// 高精度+搜索+质数 BZOJ1225 [HNOI2001] 求正整数 // 思路: // http://blog.csdn.net/huzecong/article/details/847868 ...
- (转载)OC学习篇之---类的三大特性:封装,继承,多态
之前的一片文章介绍了OC中类的初始化方法和点语法的使用,今天来继续学习OC中的类的三大特性,我们在学习Java的时候都知道,类有三大特性:继承,封装,多态,这个也是介绍类的时候,必须提到的话题,那么今 ...
- Annotations:注解
注解,作为元数据的一种形式,虽不是程序的一部分,却有以下作用: 可以让编译器跳过某些检测 某些工具可以根据注解信息生成文档等 某些注解可以在运行时检查 @表示这是一个注解 @Override ...
- 一个考察for循环题 讨论一下
一道Java程序题,主要是考察for循环如下所示: public class Test { static boolean fun(char c) { System.out.print(c); retu ...