在前面两个题目中,我们讨论了被称为"NIM(拈)“的这种游戏及其变种玩法和必胜策略,下面我们将讨论这类游戏的另一种有趣玩法。假设有两堆石头,有两个玩家会根据如下的规则轮流取石头。每人每次可以从两堆石头中各取数量相等的石头,或者仅从一堆石头中取出任意数量的石头;最后把剩下的石头一次拿光的人获胜。定义一个函数如下: bool nim(n,m) //n,m两堆石头的数量。要求返回一个布尔值,表明首先取石头的玩家是否能赢得这个游戏。
解法一:和朴素的素数筛选法一样,从最小数量的石头堆开始逐个排查,比如(1,2)这个石头堆就是不安全局面,所以这个数字组合就相当于排查出一个质数来,一次以此类推。自底向上的直到排查出所有不安全局面为止。
自己写的代码如下:(时间复杂度为O(n3))
#include <iostream>
using namespace std;
#define M 91//分别输入两堆石头的数量
#define N 56//分别输入两堆石头的数量
const int n=M>N?M:N;
struct Stone
{
int a;
int b;
int flag;
}s[n][n];
//两堆石头的游戏
bool NIM(int ,int )
{
int i,j,t=n,i1=1,j2=2;
while(t--)
{
int FLAG=0;
for (i=0;i<n;i++)
{
for (j=i;j<n;j++)
{
if (s[i][j].a==s[i][j].b)
{
s[i][j].flag=1;//标志为1的项都是先手必胜的项
}
if (s[i][j].a==i1||s[i][j].a==j2||s[i][j].a-i1==s[i][j].b-j2)
{
if (s[i][j].a<=i1&&s[i][j].b<=j2)
{
continue;
}
s[i][j].flag=1;
}
}
}
for (i=0;i<n;i++)
{
for (j=i+1;j<n;j++)
{
if (s[i][j].flag==0&&s[i][j].a>i1&&s[i][j].b>j2)
{
i1=s[i][j].a;
j2=s[i][j].b;
cout<<"("<<i1<<","<<j2<<") ";
FLAG=1;
if ((s[i][j].a==M&&s[i][j].b==N)||(s[i][j].a==N&&s[i][j].b==M))
{
return false;
}
break;
}
}
if (FLAG==1)
{
break;
}
}
}
return true;
}
void main()
{
int i,j;
for (i=n-1;i>=0;i--)
{
for (j=i;j<n;j++)
{
s[i][j].a=i+1;
s[i][j].b=j+1;
}
}
if(NIM(M,N))
{
cout<<"先手必赢"<<endl;
}
else
{
cout<<"先手必输"<<endl;
}
}
解法二:看到书上所给的一个公式,利用这个公式就可以求出所有不安全局面,但是对于这个公式的数学证明没看懂,我想真正面试的时候,我也不可能想到这种方法。
根据这个公式写得代码如下:(时间复杂度为O(1))
#include <iostream>
#include <math.h>
using namespace std;
bool nim(int x,int y)//whether win the game
{
double a,b;
a=(1+sqrt(5.0))/2;
b=(3+sqrt(5.0))/2;
if (x==y)
{
return true;
}
if (x>y)
{
swap(x,y);//ensure x<=y
}
if (x==(int)((y-x)*a)&&y==(int)((y-x)*b))
{
return false;
}
else return true;
}
void nim(int num)//print all the unsafe points
{
double a,b;
a=(1+sqrt(5.0))/2;
b=(3+sqrt(5.0))/2;
for (int i=0;i<=num;i++)
{
cout<<"("<<(int)(a*i)<<","<<(int)(b*i)<<") ";
}
cout<<endl;
}
void main()
{
cout<<nim(6,10)<<endl;
nim(100);
}原文:http://blog.csdn.net/z84616995z/article/details/39154675