第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个苹果, ...
随机推荐
- linux常用命令之--目录与文件的操作命令
1.linux的目录与文件的增.删.改.复制 pwd:用于显示当前所在的目录 ls:用于显示指定目录下的内容 其命令格式如下: ls [-option] [file] 常用参数: -l:显示文件和目录 ...
- Tableau学习笔记之一
书本:Tableau数据可视化实战,Ashutosh Nandeshwar著 学习时主要采用Tableau Desktop 9.0,由于该软件是商业软件,价格不菲,故只能试用,期限为14天,可以通过修 ...
- linux中配置桥接网络,让虚拟机能够上网
使用桥接模式最主要的目的就是让虚拟机也能上网,从而有了这篇文章. 1.设置虚拟机的网络连接方式 在设置虚拟机网线的连接方式的时候,注意第一个选择桥接模式,第二个界面名称必须使用和宿主机相同的网卡,然后 ...
- linux-制作linux启动U盘
1. 使用的制作工具 Ø 下载需要制作启动盘的linux的iso文件 Ø 制作启动盘的软件linux usb creater Ø U盘(大小差不多需要4G的空间) 软件可以的下载的地址:http:// ...
- 用Python操作Mysql
平时的主要编程语言是Java,开发时也主要用Mysql,经常为了测试,调试的目的需要操作数据库,比如备份,插入测试数据,修改测试数据,有些时候不能简单的用SQL就能完成任务,或都很好的完成任务,用Ja ...
- c++ 概念及学习/c++ concept&learning(二)
上篇内容讲述了整个语言的发展[为什么会产生编程语言],以及学习C++所需要掌握的内容.这节开始认识第一部分最基本的内容:C++的内建类型,也就是基本类型. 在这些知识之前留一个问题:为什么基本所有语言 ...
- mysql 查看锁表解锁
-- 查看那些表锁到了show OPEN TABLES where In_use > 0;-- 查看进程号show processlist;--删除进程 kill 42236:
- Spring EL Operators example
Spring EL supports most of the standard mathematical, logical or relational operators. For example, ...
- UVaLive 6627 First Date (转换时间)
题意:给定两个日期,两种不同算闰年的方法,导致日期不同,给定那个慢的,求你求了那个快的. 析:因为算闰年的方法不同,所以我们就要先从1582算到当前时间,算出差了多少天,再加上就好.注意跨月,跨年的情 ...
- hibernate[版本四]知识总结
1.hibernate是orm对象关系映射,是对jdbc的封装 2.hibernate版helloworld 2.1导入jar <dependencies> <dependency& ...