编程之美 - 抓石头游戏(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;

    原文作者:zy__
    原文地址: https://blog.csdn.net/wangzhiyu1980/article/details/50852985
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞