編程之美 - 抓石頭遊戲(3)

遊戲規則: 
有兩堆石頭,玩家A 和 B,兩個人可以從一堆石頭中取任意數目的石頭或從兩堆石頭中取相同數量的石頭。
最後取得所有石頭的人勝。

書中的分析
從最簡單的情況入手
只有一塊石頭  == >  先拿的一定會贏。
如果兩堆石頭數目相等   X:X  ==> 先拿的一定會
如果兩堆石頭數目爲 1 和 2  ==> 先拿的一定會

那麼1 和 2 就是我們找到的第一個希望留給對手的組合。

書中使用了表格的形式來進行分析,非常的有效。

1,1 1,2 1,3 1,4 1,5 1,6 1,7 1,8 1,9 1,10

2,2 2,3 2,4 2,5 2,6 2,7 2,8 2,9 2,10


3,3 3,4 3,5 3,6 3,7 3,8 3,9 3,10



4,4 4,5 4,6 4,7 4,8 4,9 4,10




5,5 5,6 5,7 5,8 5,9 5,10





6,6 6,7 6,8 6,9 6,10






7,7 7,8 7,9 7,10







8,8 8,9 8,10








9,9 9,10









10,10

第一輪可以把相等的情況都去掉了, 1,2是找到的第一個目標

1,2 1,3 1,4 1,5 1,6 1,7 1,8 1,9 1,10


2,3 2,4 2,5 2,6 2,7 2,8 2,9 2,10



3,4 3,5 3,6 3,7 3,8 3,9 3,10




4,5 4,6 4,7 4,8 4,9 4,10





5,6 5,7 5,8 5,9 5,10






6,7 6,8 6,9 6,10







7,8 7,9 7,10








8,9 8,10









9,10











第二輪可以去掉經過一次抓取可以將數目轉換爲1,2的所有的組合。相當於把必輸的情況留給了對手。
例如: 2,3,從兩堆各取1個石頭,便成爲了 1,2


1,2






















3,5 3,6 3,7 3,8 3,9 3,10





4,6 4,7 4,8 4,9 4,10






5,7 5,8 5,9 5,10







6,8 6,9 6,10








7,9 7,10









8,10





















經過第二輪後,3,5便是找到的第二組。
然後,第三輪就是去掉經過一次抓取可以將數目轉換爲1,2 或 3,5 的所有的組合。 又找到了 4,7


1,2






















3,5











4,7 4,8 4,9 4,10


















6,9 6,10









7,10































最後 在10以內的找到了  6,10。
[1,2]  [3,5]  [4,7]  [6,10]


總結成表:

i 1 2 3 4
x 1 3 4 6
y 2 5 7 10

這裏可以看到兩個規律
  • y = x + i
  • x 和 y 的集合是一個正整數的集合,而且不重複。


程序示例:

#include <iostream>
#include <vector>

using namespace std;

int getIndex(vector<int> arr, int val)
{
    int i = 0;
    for (i = 0; i < arr.size(); i++)
    {
        if (arr[i] == val) return i;
    }
    return -1;
}

bool calc(int x, int y)
{
    int t = 0, n = 1, i = 0;
    int delt = 1;
    vector<int> arrA;

    if (x == y)
        return true;

    if ((x == 1) && (y == 2))
        return false;

    if (x > y)
    {
        t = x; x = y; y = t;
    }

    arrA.push_back(2);
    cout << "[" << 1 << "," << 2 << "]" << endl;

    while(n < x)
    {
        while(getIndex(arrA, ++n) != -1);
        delt++;

        cout << "[" << n << "," << n + delt << "]" << endl;
        arrA.push_back(n + delt);
    }
    cout << endl;

    if ((n == x) && y==(n+delt))
        return false;
    else
        return true;

    return true;
}

int main()
{
    int i = 0;
    int Xs[] = {1, 1, 3, 3, 4, 6, 9,};
    int Ys[] = {1, 2, 5, 7, 8, 10, 10};
    int len = sizeof(Xs)/sizeof(Xs[0]);

    for (i = 0; i < len; i++)
    {
        if (calc(Xs[i], Ys[i]))
        {
            cout << Xs[i] << ":" << Ys[i] << "   win!" << endl;
        }
        else
        {
            cout << Xs[i] << ":" << Ys[i] << "   loose!" << endl;
        }

        cout << "=========================" << endl;
    }

    cin >> i;
    return 0;
}





測試結果:

1:1   win!
=========================
1:2   loose!
=========================
[1,2]
[3,5]

3:5   loose!
=========================
[1,2]
[3,5]

3:7   win!
=========================
[1,2]
[3,5]
[4,7]

4:8   win!
=========================
[1,2]
[3,5]
[4,7]
[6,10]

6:10   loose!
=========================
[1,2]
[3,5]
[4,7]
[6,10]
[8,13]
[9,15]

9:10   win!
=========================



備註:書中在最後判斷 x,y 是否會贏時用的代碼有些問題:
原來的代碼是

if ((n != x) || getIndex(arrA, y) != -1)
    return true; 
else
    return false;


在測試用例 x = 9, y = 10時會失敗,[6, 10]   [9, 15]是兩對組合,當x = 9時, 10也在arrA中,這時用getIndex(y)取得的值不是-1,判斷是反的。


修改了一下
   if ((n == x) && y==(n+delt))
        return false;
    else
        return true;



發佈了235 篇原創文章 · 獲贊 31 · 訪問量 59萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章