我们强烈建议参考以下文章作为先决条件。
组合博弈论 |第一套(介绍)
在这篇文章中,讨论了 Nim 游戏。 Nim 游戏由以下规则描述-
“给定许多堆,其中每堆包含一定数量的石头/硬币。在每一回合中,玩家只能选择一堆并从该堆中取出任意数量的石头(至少一个)。不能移动的玩家被认为输掉了游戏(即,拿最后一块石头的人是赢家)。 ”
例如,假设有两个玩家 – A和B ,最初有三堆硬币,最初每堆硬币分别有3、4、5 个硬币,如下所示。我们假设第一步是由A 进行的。看下图可以清楚的了解整个游戏玩法。
A 赢了比赛(注:A 迈出了第一步)
那么A在这个游戏中拥有很强的专业知识吗?或者他/她先开始比B有优势?
现在让我们再次玩,使用与上述相同的桩配置,但这次B先开始而不是A 。
B 赢了比赛(注:B 迈出了第一步)
B Y形如上图所示,必须明确的是,比赛取决于一个重要因素-谁先开始游戏?
那么,先开始的玩家每次都会赢吗?
让我们再次玩游戏,从A开始,这次使用不同的初始配置。这些堆最初有 1、4、5 个硬币。
A会在他先发的情况下再次获胜吗?让我们来看看。
A 迈出了第一步,但输掉了比赛。
所以,结果很明显。 A输了。但是如何?我们知道这个游戏很大程度上取决于哪个玩家先开始。因此,一定有另一个因素主导了这个简单而有趣的游戏的结果。该因素是堆/堆的初始配置。这次的初始配置与上一次不同。
所以,我们可以得出结论,这个游戏取决于两个因素——
- 先开始的玩家。
- 桩/堆的初始配置。
事实上,我们甚至可以在玩游戏之前就预测游戏的赢家!
Nim-Sum :在游戏的任何时刻,每堆/堆中硬币/石头数量的累积 XOR 值在该点称为 Nim-Sum。
“如果A和B都以最佳方式进行游戏(即他们没有犯任何错误),那么如果游戏开始时的 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 现场工作专业课程和学生竞争性编程现场课程。