POJ-1067 取石子游戏(威佐夫博弈)

原题:POJ 取石子游戏

部分观点参考了:
威佐夫博弈,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;
}

猜你喜欢

转载自blog.csdn.net/qq_26558047/article/details/85001505
今日推荐