📜  组合博弈论 |第 2 组(尼姆游戏)

📅  最后修改于: 2021-09-24 05:02:44             🧑  作者: Mango

我们强烈建议参考以下文章作为先决条件。

组合博弈论 |第一套(介绍)

在这篇文章中,讨论了 Nim 游戏。 Nim 游戏由以下规则描述-

给定许多堆,其中每堆包含一定数量的石头/硬币。在每一回合中,玩家只能选择一堆并从该堆中取出任意数量的石头(至少一个)。不能移动的玩家被认为输掉了游戏(即,拿最后一块石头的人是赢家)。

例如,假设有两个玩家 – AB ,最初有三堆硬币,最初每堆硬币分别有3、4、5 个硬币,如下所示。我们假设第一步是由A 进行的。看下图可以清楚的了解整个游戏玩法。

Set2_Nim_Game_start_with_A
A 赢了比赛(注:A 迈出了第一步)

那么A在这个游戏中拥有很强的专业知识吗?或者他/她先开始比B有优势?

现在让我们再次玩,使用与上述相同的桩配置,但这次B先开始而不是A

gameofnim11
B 赢了比赛(注:B 迈出了第一步)

B Y形如上图所示,必须明确的是,比赛取决于一个重要因素-谁先开始游戏?

那么,先开始的玩家每次都会赢吗?
让我们再次玩游戏,从A开始,这次使用不同的初始配置。这些堆最初有 1、4、5 个硬币。

A会在他先发的情况下再次获胜吗?让我们来看看。
gameofnim4
A 迈出了第一步,但输掉了比赛。

所以,结果很明显。 A输了。但是如何?我们知道这个游戏很大程度上取决于哪个玩家先开始。因此,一定有另一个因素主导了这个简单而有趣的游戏的结果。该因素是堆/堆的初始配置。这次的初始配置与上一次不同。

所以,我们可以得出结论,这个游戏取决于两个因素——

  1. 先开始的玩家。
  2. 桩/堆的初始配置。

事实上,我们甚至可以在玩游戏之前就预测游戏的赢家!

Nim-Sum :在游戏的任何时刻,每堆/堆中硬币/石头数量的累积 XOR 值在该点称为 Nim-Sum。

“如果AB最佳方式进行游戏(即他们没有犯任何错误),那么如果游戏开始时的 Nim-Sum 非零,则保证先开始的玩家获胜。否则,如果 Nim-Sum 评估为零,那么玩家A肯定会输。”

有关上述定理的证明,请参阅- https://en.wikipedia.org/wiki/Nim#Proof_of_the_driving_formula

最优策略:

    理解最优策略所必需的关于按位异或的一些推论:
  • 如果“n”个数字的 XOR 和已经为零,则不可能通过对数字进行一次归约来使 XOR 和为零。
  • 如果“n”个数字的 XOR 和不为零,那么至少有一种方法可以减少一个数字,XOR 和为零。

最初可能存在两种情况。

情况 1:初始 Nim Sum 为零
正如我们所知,在这种情况下,如果以最佳方式进行B赢,这意味着B总是希望轮到A 时让 Nim 和为零。
因此,由于 Nim Sum 最初为零,因此A移除新 Nim Sum 的项目数量将不为零(如上所述)。此外,由于B更喜欢轮到A的 Nim 和为零,然后他会下棋以再次使 Nim 和为零(这总是可能的,如上所述)。
只要有任何一堆物品,游戏就会运行,并且在它们各自的每一轮中, A将使 Nim 和非零, B将使其再次为零,最终将没有元素剩余,而B是一个选择最后一个赢得比赛。

从上面的解释可以明显看出,每个玩家的最佳策略是在他们的每个回合中让对手的 Nim Sum 为零,如果它已经为零,这是不可能的。

情况 2:初始 Nim Sum 非零
现在采用最优方法A将使 Nim Sum 现在为零(这是可能的,因为初始 Nim 和不为零,如上所述)。现在,在B“轮到作为NIM总和已经是零时什么号B选秀权,净息差总和将是非零和A可以选择一个号码,使净息差和零一次。只要任何堆中都有可用的物品,这就会持续下去。
A将是选择最后一项的人。

因此,正如在上述案例中所讨论的,现在应该很明显,任何玩家的最佳策略是使 nim sum 为零,如果它不为零,如果它已经为零,那么玩家现在所做的任何移动,它都可以被反击.

让我们在上面玩的游戏中应用上述定理。在第一场比赛中, A首先开始,比赛开始时的Nim-Sum是,3 XOR 4 XOR 5 = 2,这是一个非零值,因此A赢了。而在第二局中,当桩的初始配置为1、4和5, A先开始时, A就注定输了,因为游戏开始时的Nim-Sum是1 XOR 4 XOR 5 = 0。

执行:

在下面的程序中,我们在计算机和人类(用户)之间玩 Nim-Game
下面的程序使用两个函数
knowWinnerBeforePlaying() ::在播放前告诉结果。
playGame() :玩完整个游戏并最终宣布获胜者。函数playGame() 不接受人类(用户)的输入,而是使用 rand()函数随机捡起一堆并从捡到的堆中随机移除任意数量的石头。

通过删除 rand()函数并插入 cin 或 scanf() 函数,可以修改以下程序以接收用户的输入。

C++
/* A C++ program to implement Game of Nim. The program
assumes that both players are playing optimally */
#include 
#include 
using namespace std;
  
#define COMPUTER 1
#define HUMAN 2
  
/* A Structure to hold the two parameters of a move
  
A move has two parameters-
1) pile_index = The index of pile from which stone is
                    going to be removed
2) stones_removed = Number of stones removed from the
                        pile indexed = pile_index */
struct move
{
    int pile_index;
    int stones_removed;
};
  
/*
piles[] -> Array having the initial count of stones/coins
            in each piles before the game has started.
n     -> Number of piles
  
The piles[] are having 0-based indexing*/
  
// A C function to output the current game state.
void showPiles (int piles[], int n)
{
    int i;
    cout <<"Current Game Status -> ";
    for (i=0; i 0)
                non_zero_indices [count++] = i;
  
        (*moves).pile_index = (rand() % (count));
        (*moves).stones_removed =
                1 + (rand() % (piles[(*moves).pile_index]));
        piles[(*moves).pile_index] =
        piles[(*moves).pile_index] - (*moves).stones_removed;
  
        if (piles[(*moves).pile_index] < 0)
            piles[(*moves).pile_index]=0;
    }
    return;
}
  
// A C function to play the Game of Nim
void playGame(int piles[], int n, int whoseTurn)
{
    cout <<"\nGAME STARTS\n\n";
    struct move moves;
  
    while (gameOver (piles, n) == false)
    {
        showPiles(piles, n);
  
        makeMove(piles, n, &moves);
  
        if (whoseTurn == COMPUTER)
        {
            cout <<"COMPUTER removes" << moves.stones_removed << "stones from pile at index " 
             << moves.pile_index << endl;
            whoseTurn = HUMAN;
        }
        else
        {
            cout <<"HUMAN removes"<< moves.stones_removed << "stones from pile at index " 
            << moves.pile_index << endl;
            whoseTurn = COMPUTER;
        }
    }
  
    showPiles(piles, n);
    declareWinner(whoseTurn);
    return;
}
  
void knowWinnerBeforePlaying(int piles[], int n,
                            int whoseTurn)
{
    cout <<"Prediction before playing the game -> ";
  
    if (calculateNimSum(piles, n) !=0)
    {
        if (whoseTurn == COMPUTER)
            cout <<"COMPUTER will win\n";
        else
            cout <<"HUMAN will win\n";
    }
    else
    {
        if (whoseTurn == COMPUTER)
            cout <<"HUMAN will win\n";
        else
            cout <<"COMPUTER will win\n";
    }
  
    return;
}
  
// Driver program to test above functions
int main()
{
    // Test Case 1
    int piles[] = {3, 4, 5};
    int n = sizeof(piles)/sizeof(piles[0]);
  
    // We will predict the results before playing
    // The COMPUTER starts first
    knowWinnerBeforePlaying(piles, n, COMPUTER);
  
    // Let us play the game with COMPUTER starting first
    // and check whether our prediction was right or not
    playGame(piles, n, COMPUTER);
  
    /*
    Test Case 2
    int piles[] = {3, 4, 7};
    int n = sizeof(piles)/sizeof(piles[0]);
  
    // We will predict the results before playing
    // The HUMAN(You) starts first
    knowWinnerBeforePlaying (piles, n, COMPUTER);
  
    // Let us play the game with COMPUTER starting first
    // and check whether our prediction was right or not
    playGame (piles, n, HUMAN); */
  
    return(0);
}
  
// This code is contributed by shivanisinghss2110


C
/* A C program to implement Game of Nim. The program
   assumes that both players are playing optimally */
#include 
#include 
#include 
  
#define COMPUTER 1
#define HUMAN 2
  
/* A Structure to hold the two parameters of a move
  
 A move has two parameters-
  1) pile_index = The index of pile from which stone is
                    going to be removed
  2) stones_removed = Number of stones removed from the
                        pile indexed = pile_index  */
struct move
{
    int pile_index;
    int stones_removed;
};
  
/*
 piles[] -> Array having the initial count of stones/coins
            in each piles before the game has started.
 n       -> Number of piles
  
 The piles[] are having 0-based indexing*/
  
// A C function to output the current game state.
void showPiles (int piles[], int n)
{
    int i;
    printf ("Current Game Status -> ");
    for (i=0; i 0)
                non_zero_indices [count++] = i;
  
        (*moves).pile_index = (rand() % (count));
        (*moves).stones_removed =
                 1 + (rand() % (piles[(*moves).pile_index]));
        piles[(*moves).pile_index] =
         piles[(*moves).pile_index] - (*moves).stones_removed;
  
        if (piles[(*moves).pile_index] < 0)
            piles[(*moves).pile_index]=0;
    }
    return;
}
  
// A C function to play the Game of Nim
void playGame(int piles[], int n, int whoseTurn)
{
    printf("\nGAME STARTS\n\n");
    struct move moves;
  
    while (gameOver (piles, n) == false)
    {
        showPiles(piles, n);
  
        makeMove(piles, n, &moves);
  
        if (whoseTurn == COMPUTER)
        {
            printf("COMPUTER removes %d stones from pile "
                   "at index %d\n", moves.stones_removed,
                   moves.pile_index);
            whoseTurn = HUMAN;
        }
        else
        {
            printf("HUMAN removes %d stones from pile at "
                   "index %d\n", moves.stones_removed,
                   moves.pile_index);
            whoseTurn = COMPUTER;
        }
    }
  
    showPiles(piles, n);
    declareWinner(whoseTurn);
    return;
}
  
void knowWinnerBeforePlaying(int piles[], int n,
                             int whoseTurn)
{
    printf("Prediction before playing the game -> ");
  
    if (calculateNimSum(piles, n) !=0)
    {
        if (whoseTurn == COMPUTER)
            printf("COMPUTER will win\n");
        else
            printf("HUMAN will win\n");
    }
    else
    {
        if (whoseTurn == COMPUTER)
            printf("HUMAN will win\n");
        else
            printf("COMPUTER will win\n");
    }
  
    return;
}
  
// Driver program to test above functions
int main()
{
    // Test Case 1
    int piles[] = {3, 4, 5};
    int n = sizeof(piles)/sizeof(piles[0]);
  
    // We will predict the results before playing
    // The COMPUTER starts first
    knowWinnerBeforePlaying(piles, n, COMPUTER);
  
    // Let us play the game with COMPUTER starting first
    // and check whether our prediction was right or not
    playGame(piles, n, COMPUTER);
  
    /*
    Test Case 2
    int piles[] = {3, 4, 7};
    int n = sizeof(piles)/sizeof(piles[0]);
  
    // We will predict the results before playing
    // The HUMAN(You) starts first
    knowWinnerBeforePlaying (piles, n, COMPUTER);
  
    // Let us play the game with COMPUTER starting first
    // and check whether our prediction was right or not
    playGame (piles, n, HUMAN);    */
  
    return(0);
}


输出:在不同的运行中可能会有所不同,因为随机数用于决定下一步(对于失败的玩家)。

Prediction before playing the game -> COMPUTER will win

GAME STARTS

Current Game Status -> 3 4 5 
COMPUTER removes 2 stones from pile at index 0
Current Game Status -> 1 4 5 
HUMAN removes 3 stones from pile at index 1
Current Game Status -> 1 1 5 
COMPUTER removes 5 stones from pile at index 2
Current Game Status -> 1 1 0 
HUMAN removes 1 stones from pile at index 1
Current Game Status -> 1 0 0 
COMPUTER removes 1 stones from pile at index 0
Current Game Status -> 0 0 0 

COMPUTER won

参考 :
https://en.wikipedia.org/wiki/Nim

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程