Nim游戏

    “本世纪初,哈佛大学数学系副教授查理士.理昂纳德.包顿 (Chales Leonard Bouton) 提出一篇极详尽的分析和证明,利用数的二进位表示法,解答了这个游戏的一般法则:对任意列数的铜板,每列有任意枚数,如何取得致胜之道?

在包顿的术语中,拿过后剩下的残局不是安全 (safe) 就是不安全 (unsafe) 的局面。在所有安全的情况下,不管对方如何拿总是到一不安全的情况,你可以再取适当枚数的铜板(在适当的某一列),达到另一安全的情况,这样一直到拿光铜板为止,当然最后一次拿光铜板的一定是你。反之,你如果留下不安全的情况,对方必有方法在适当的某一列,取走适当枚数的铜板,达到他的安全情况,也就是说你输定了。

包顿的方法很简单。首先,将各列铜板的枚数化成二进位数,相加,但不进位,然后再看和的各个位数。如果和的各个位数都是偶数,则表示一安全残局;否则,如果有一位是奇数,则为不安全残局。例如「三、四、五」游戏,一开始就是不安全残局,先拿的人可以适当取二枚而造成他的安全残局。 “



为什麼安全残局和不安全残局可以利用上述的方法判定呢?这个道理其实很简单。首先,如果将各列铜板数化为二进位表示法,相加,但不进位,得到的各个位数都是偶数的话,不论对方取那一列,多少枚铜板,则那一列铜板数所对应的二进位表示法中,必有某一位或数位由 0 变成 1 或者由 1 变成 0,其相加的和也相对的有某一位或数位由偶数变成奇数

相反的,如果和的某一位或数位是奇数,则我们有办法在某一列取走适当枚数的铜板,使得新的和的各个位数都是偶数。首先,选取和中所有为奇数的各个位数;其次看这些位数中那一个是最左边一位;找某一列,使其二进位表示法在此位上刚好是 1;然后将此列的铜板数所对应的二进位数中,凡是第 ai 位,都改变其数值,亦即若为 0 则变为 1,若为 1 则变为 0,如此得到一新数,我们只要在此列铜板取走适当的数目,使达到这新的枚数,即可以使新的和的各个位数都是偶数;
(摘抄《百度知道》)

根据以上思路,我写了一个关于给定一个数列,判断其是否为安全残局,如果不是,改变一个数使其成为安全残局的程序。源码如下:
#include 
#include 
#include 

using namespace std;


#define BIT_SIZE 8

//
// 判定給定的數列是否為安全數列
// 如果它們的異或值為0,則為安全數列,否則為不安全數列
// 
bool IsNumsSafe(const vector &nums)
{
	int result = nums[0];
	if (nums.size() > 1)
	{
		for (int i = 1; i < nums.size(); ++i)
		{
			result ^= nums[i]; 
		}
	}

	if (result == 0)
	{
		return true;
	}

	return false;
}


//
// 顯示數列
//
void ShowNums(const vector &nums)
{
	for (int i = 0; i < nums.size(); ++i)
	{
		cout << nums[i] << " ";
	}
	cout << endl;
}


//
// 將一個整數數列轉換為二進制表示的數列
//
vector< bitset > ChangeNumsToBitnums(const vector &nums)
{
	vector< bitset > numBits;
	for (int i = 0; i < nums.size(); ++i)
	{
		bitset bNum(nums[i]);
		numBits.push_back(bNum);
	}

	return numBits;
}

//
// 計算所有二進制數列中每個位上的數字之和
// 如1010、0110、0010的每個位上的和分別為1、1、3、0
//
void CaculateBitnumsBitSum(const vector< bitset > &numBits, int *bitSum)
{
	for (int i = 0; i < BIT_SIZE; ++i)
	{
		bitSum[i] = 0;
		for (int j = 0; j < numBits.size(); ++j)
		{
			bitSum[i] += numBits[j][i];
		}
	}

}


//
// 找到數列中需要改變的數字
//
int FindNumToChange(const int* bitSum, const vector< bitset > &numBits)
{
	// 從高位往低位找,找到第一個奇數對應的位
	int i = 0;
	int ddPos = -1;
	for (i = BIT_SIZE - 1; i >= 0; --i)
	{
		if (bitSum[i] % 2 != 0)
		{
			ddPos = i;
			break;
		}
	}

	// 找到奇數位對應為數字1的那個數,這個數就是需要改變的數
	int selectIndex = -1;
	for (i = 0; i < numBits.size(); ++i)
	{
		if (numBits[i].test(oddPos))
		{
			selectIndex = i;
			break;
		}
	}

	return selectIndex;
}


//
// 將一系列給定的數字轉換為安全數列
// 如果數列已經是安全數列,則不做變更
// 否則,減小其中一個數字,使數列成為安全數列
//
bool ChangeNumbersToSafe(vector &nums)
{
	if (nums.size() < 1)
	{
		cout << "給定的數列錯誤!至少要給一個數!" << endl;
		return false;
	}

	// 先判斷給定的數字是否已經為安全數組
	if (IsNumsSafe(nums))
	{
		cout << "給定的數列已經是安全的,無須更改任何數字!" << endl;
		return false;
	}
	
	// 將數列轉換為二進制形式
	vector< bitset > numBits;
	numBits = ChangeNumsToBitnums(nums);

	int bitSum[BIT_SIZE];
	CaculateBitnumsBitSum(numBits, bitSum);

	int selectIndex = -1;
	selectIndex = FindNumToChange(bitSum, numBits);

	// 計算除了需要改變的數字外所有的二進制數上每個位上的數字和
	int i = 0;
	for (i = 0; i < BIT_SIZE; ++i)
	{
		bitSum[i] -= numBits[selectIndex][i];
	}

	// 找出可以使每個位上的和為偶數的數字
	bitset myBitNum;
	for (i = BIT_SIZE - 1; i >= 0; --i)
	{
		if (bitSum[i] % 2 != 0)
		{
			myBitNum.set(i);
		}
	}
	
	// 將二進制數轉換為整型數字
	nums[selectIndex] = myBitNum.to_ulong();

	return true;
}

//
// 主函數
//
int main()
{
	vector nums;

	nums.push_back(1);
	nums.push_back(3);
	nums.push_back(4);
	nums.push_back(5);
	ShowNums(nums);
	ChangeNumbersToSafe(nums);
	ShowNums(nums);

	return 0;
}

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/24494482/viewspace-672211/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/24494482/viewspace-672211/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值