部分观点参考了:
威佐夫博弈,Beatty定理和黄金分割数
在没有得知Beatty定理和著名的威佐夫博弈时,自己对这个问题进行了分析:
游戏看似简单,其实证明非常复杂;
重点在于以下的话:
游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。
一开始的想法是列出必输的两堆,以下为必输数对:
推着些必输数对需要花一点时间,不是一下子就能想到的;
然后分析必输数对,寻找规律;
经过个人分析确实是有一点规律;
如下为规律:
观察图里的褐色圈,发现必输数对都是有递增关系;
所以可以尝试用暴力算法;
于是有了我打的一段试错代码:
#include "stdafx.h"
#include<iostream>
using namespace std;
int bishu(int);
int main()
{
int m, n; //定义两个数,分别为两堆石头;
while ( cin>>m>>n )
{
if (m==n) //如果两堆石头数字都一样,那么肯定赢(这里没考虑到0,0,不过不改了,因为是试错代码)
{
cout << "1" << endl;
continue;
}
else //如果两堆石头数量不同
{
int min,max; //找出两个数之中比较小的一个
if (m > n)
{
min = n; max = m;
}
else
{
min = m; max = n;
}
if (min==0)
{
cout << "1" << endl;
continue;
}
if (min==1) //如果最小为1:
{
if (max == 2)
{
cout << "0" << endl;
continue;
}
else
{
cout << "1" << endl;
continue;
}
}
else //如果最小不为1;
{
if(max==bishu(min))
{
cout << "0" << endl;
continue;
}
else
{
cout << "1" << endl;
continue;
}
}
}
}
return 0;
}
int bishu(int k)
{
int *a = new int[k]; //创建一个有k个数字的数组;
int shu; //用来记录必然输掉的数字;
a[0] = 2; //1 2这两堆石头,必输;
int key = 2; //加入key
for (int i = 1; i <= k-1; i++) //记得i+1
{
bool flag = true; //设置变化开关;
for (int j = 0; j < i; j++)
{
if (a[j] == i + 1) //如果有一个数组里面的数字等于轮到的数字,那么就不加key;
{
a[i] = j + 1; //把j+1赋值给a[i](因为数组从0开始);
flag = false; //开关关闭;
break;
}
}
if (flag) //如果开关没有关闭过的话;
{
a[i] = i + 1 + key;
key++; //key的每次都要变;
}
}
shu = a[k - 1];//记录必定输掉的数字;
delete[]a;
return shu;
}
通过一些测试,测试得到的结果是可以通过的(还需要进一步修改)
不过这段代码并不能AC,因为他是暴力算法,
两堆石头的数量是1,000,000,000左右;
如此庞大的数组量,肯定会超时;
所以,只能上网查阅了一下资料,知道了这其实是个著名的“威佐夫博弈”问题
要运用到一个叫做Beatty(贝蒂定理)的数学定理
Beatty定理
设a,b是正无理数且1/a+1/b=1
记P=[na]|n为任意的正整数,Q=[nb]|n为任意的正整数
([x]指的是取x的整数部分)
则P与Q是Z+的一个划分,即P∩Q为空集且P∪Q为正整数集合N+
取正无理数α,β,使得
1/α+1/β=1
构造两个数列an,bn,通项分别为
an=[αn]
bn=[βn]
an为小于等于αn的最大整数
bn为小于等于βn的最大整数
这贝蒂定理可以看出非常符合取石子游戏的规则
取石子游戏中的所有 必输数对 数字不会重复 而且 逐步递增
所以是 遍历所有正整数数字的
而贝蒂定理中的数组an,bn也是遍历所有正整数的
————所以用贝蒂定理——————
假设两堆石头的差值是 k ;
an=[αk],bn=[βk]
an+k=[(α+1)k]=[βk]
所以α+1=β
1/(α+1)+1/α=1
α=(sqrt(5)+1)/2
这样就得到了必输数对的公式
an =(sqrt(5)+1)*k / 2
bn = an + k
而有趣的是 (sqrt(5)+1)/2 是黄金分割数 1.618
到达了这一步,代码就非常简单了:
以下是修改后的AC代码;
#include "stdafx.h"
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
int m, n;
while (cin>>m>>n)
{
int cha,max,min;
if (m > n)
{
cha = m - n;
min = n;
max = m;
}
else
{
cha = n - m;
min = m;
max = n;
}
int an, bn;
an = 0.5*cha*(1 + sqrt(5.0));
bn = an + cha;
if (min==an && bn==max)
{
cout << "0" << endl;
}
else
{
cout << "1" << endl;
}
}
return 0;
}