問題:
有N塊石頭和兩個玩家A和B,玩家A先將石頭隨機分成若干堆,然後按照BABA...的順序不斷輪流取石頭,能將剩下的石頭一次取光的玩家獲勝,每次取石頭時,每個玩家只能從若干堆石頭中任選一堆,取這一堆石頭中任意數目(大於0)個石頭。
請問:
玩家A要怎樣分配和取石頭才能保證自己有把握取勝?
如果石頭的個數N爲偶數,A只要將其分爲相同的兩份,就一定能取勝。
初始:XOR(M1, M1) == 0
玩家B:XOR(M1, M2) != 0 (其中一堆的個數減少到M2)
玩家A:XOR(M2, M2) == 0 (玩家A將另一堆的個數也減少到M2)
結果:XOR(M2, M2) == 0 (直到結束狀態(0, 0))
如果石頭的個數N爲奇數,B有必勝的方法。
初始:XOR(M1, M2, ... , Mn) != 0
玩家B:XOR(M1, ... , Mi', ... , Mn) == 0 (其中一堆Mi的個數減少到Mi')
玩家A:XOR(M1, ... , Mj', ... , Mn) != 0
玩家B:XOR(M1, ... , Mi', ... , Mn) == 0 (其中一堆Mi的個數減少到Mi')
結果:XOR(M1, ... , Mj' , ... , Mn) == 0 (直到結束狀態(0,0))
這裏就有個問題:已知XOR(M1, M2, ... , Mn) != 0,玩家B該改變那個Mi以使得XOR(M1, ... , Mi', ... , Mn) == 0呢?
對於這個問題的答案,書中並未準確的結論。
經過本人的分析,所得到的結論如下:
設k=XOR(M1, M2, ... , Mn),已知k!=0,取一個數Mi,其二進制表達中在k的最高二進制位上的數爲1,且這個
數Mi肯定存在(k的這個最高位在異或運算中肯定來自某一個Mi)。在程序中滿足(Mi&k) > (k>>1)條件的數即爲Mi。
簡單證明:即假設k的二進制表達是1xx,那麼Mi的二進制表達是x...x1xx,這樣玩家B將該Mi改成Xi'=XOR(Mi, k)後,
Mi'的二進制表達是x...x0yy,肯定小於Mi,並且有XOR(M1, ... , Mi', ... , Mn) == 0。
下面的程序模擬了石頭個數N爲奇數的情況,其中玩家B用我 I 表示,玩家A用She表示。
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int A[10];
int main()
{
int n = 25; // 石頭總數(可變)
int m = 5; // 劃分的堆數(可變)
int i, k, s;
int sum=0;
srand(time(0));
// 模擬m堆的石頭個數,A[i]表示第i堆石頭的個數
for (i=1; i<m; i++)
{
A[i] = rand()%(5*i-sum);
sum += A[i];
} A[m] = n - sum;
while (true)
{
// 輸出每個堆的石頭個數
for (i=1; i<=m; i++)
cout << A[i] << " ";
cout << endl;
int xor = 0;
for (i=1; i<=m; i++)
xor = xor^A[i];
for (i=1; i<=m; i++)
// 二進制表達中在k的最高二進制位上的數爲1
if ((A[i]&xor) > (xor>>1)) break;
printf("I: get %d stones from %d heap\n", A[i]-(A[i]^xor), i);
// 將Mi改爲Mi'
A[i] = A[i]^xor;
// 對方隨機取數據
for (i=1; i<=m && A[i]==0; i++);
if (i>m) break;
do
{
k = rand()%m + 1;
}while (A[k] == 0);
s = rand()%A[k] + 1;
printf("She: get %d stones from %d heap\n", s, k);
A[k] = A[k] - s;
}
return 0;
}