我们在第 1 组中介绍了组合博弈论,并在第 2 组中讨论了 Nim 游戏。
Grundy Number 是一个定义游戏状态的数字。我们可以根据 Grundy Number 定义任何公正的游戏(例如:nim 游戏)。
一旦我们使用 Sprague-Grundy 定理计算了与该游戏相关的 Grundy Numbers,Grundy Numbers 或 Nimbers 决定如何解决任何公正游戏(不仅是 Nim 游戏)。
但是在计算 Grundy Numbers 之前,我们需要了解另一个术语——Mex。
什么是墨西哥?
一组数字的“最小排除”又名“Mex”是该组中不存在的最小非负数。
如何计算Grundy Numbers?
我们使用这个定义 – 对于第一个玩家立即输掉的游戏,Grundy Number/nimber 等于 0,并且等于任何其他游戏的所有可能的下一个位置的 nimbers 的 Mex。
下面是三个示例游戏和程序,用于计算每个游戏的 Grundy Number 和 Mex。 Grundy Numbers 的计算基本上是由一个称为 calculateGrundy()函数的递归函数完成的,该函数使用 calculateMex()函数作为其子程序。
示例 1
游戏从一堆 n 块石头开始,要移动的玩家可以拿任意正数的石头。计算这个游戏的 Grundy Numbers。最后移动的玩家获胜。哪位玩家赢得了比赛?
因为如果第一个玩家有 0 个石头,他会立即输,所以 Grundy(0) = 0
如果玩家有 1 个石头,那么他可以拿走所有的石头并获胜。所以游戏的下一个可能位置(对于其他玩家)是 (0) 颗棋子
因此,Grundy(1) = Mex(0) = 1 [根据 Mex 的定义]
同样,如果一个玩家有 2 个石头,那么他可以只拿 1 个石头,或者他可以拿走所有的石头并获胜。所以游戏的下一个可能位置(对于另一个玩家)分别是 (1, 0) 颗棋子。
因此,Grundy(2) = Mex(0, 1) = 2 [根据 Mex 的定义]
同样,如果一个玩家有’n’个石头,那么他可以只拿1个石头,或者他可以拿2个石头……..或者他可以拿走所有的石头并获胜。所以游戏的下一个可能位置(对于另一个玩家)分别是 (n-1, n-2,….1) 颗棋子。
因此,Grundy(n) = Mex (0, 1, 2, ….n-1) = n [根据 Mex 的定义]
我们总结了下表中从 0 到 10 的第一个 Grundy 值-
C++
/* A recursive C++ program to find Grundy Number for
a game which is like a one-pile version of Nim.
Game Description : The game starts with a pile of n stones,
and the player to move may take any positive number of stones.
The last player to move wins. Which player wins the game? */
#include
using namespace std;
// A Function to calculate Mex of all the values in
// that set.
int calculateMex(unordered_set Set)
{
int Mex = 0;
while (Set.find(Mex) != Set.end())
Mex++;
return (Mex);
}
// A function to Compute Grundy Number of 'n'
// Only this function varies according to the game
int calculateGrundy(int n)
{
if (n == 0)
return (0);
unordered_set Set; // A Hash Table
for (int i=0; i<=n-1; i++)
Set.insert(calculateGrundy(i));
return (calculateMex(Set));
}
// Driver program to test above functions
int main()
{
int n = 10;
printf("%d", calculateGrundy(n));
return (0);
}
Java
// A recursive Java program to find Grundy
// Number for a game which is like a
// one-pile version of Nim. Game
// Description : The game starts
// with a pile of n stones, and the
// player to move may take any
// positive number of stones.
// The last player to move wins.
// Which player wins the game?
import java.util.*;
class GFG{
// A Function to calculate Mex of all
// the values in that set.
public static int calculateMex(Set Set)
{
int Mex = 0;
while (Set.contains(Mex))
Mex++;
return (Mex);
}
// A function to Compute Grundy Number
// of 'n'. Only this function varies
// according to the game
public static int calculateGrundy(int n)
{
if (n == 0)
return (0);
// A Hash Table
Set Set = new HashSet();
for(int i = 0; i <= n - 1; i++)
Set.add(calculateGrundy(i));
return (calculateMex(Set));
}
// Driver code
public static void main(String[] args)
{
int n = 10;
System.out.print(calculateGrundy(n));
}
}
// This code is contributed by divyeshrabadiya07
Python3
''' A recursive Python3 program to find Grundy Number for
a game which is like a one-pile version of Nim.
Game Description : The game starts with a pile of n stones,
and the player to move may take any positive number of stones.
The last player to move wins. Which player wins the game? '''
# A Function to calculate Mex of all the values in
# that set.
def calculateMex(Set):
Mex = 0
while (Mex in Set):
Mex += 1
return (Mex)
# A function to Compute Grundy Number of 'n'
# Only this function varies according to the game
def calculateGrundy( n):
if (n == 0):
return (0)
Set = set() # A Hash Table
for i in range(n):
Set.add(calculateGrundy(i));
return (calculateMex(Set))
# Driver program to test above functions
n = 10;
print(calculateGrundy(n))
# This code is contributed by ANKITKUMAR34
C#
// A recursive C# program to find Grundy
// Number for a game which is like a
// one-pile version of Nim. Game
// Description : The game starts
// with a pile of n stones, and
// the player to move may take
// any positive number of stones.
// The last player to move wins.
// Which player wins the game?
using System;
using System.Collections;
using System.Collections.Generic;
class GFG{
// A Function to calculate Mex of all
// the values in that set.
static int calculateMex(HashSet Set)
{
int Mex = 0;
while (Set.Contains(Mex))
Mex++;
return (Mex);
}
// A function to Compute Grundy Number
// of 'n'. Only this function varies
// according to the game
static int calculateGrundy(int n)
{
if (n == 0)
return (0);
// A Hash Table
HashSet Set = new HashSet();
for(int i = 0; i <= n - 1; i++)
Set.Add(calculateGrundy(i));
return (calculateMex(Set));
}
// Driver code
public static void Main(string []arg)
{
int n = 10;
Console.Write(calculateGrundy(n));
}
}
// This code is contributed by rutvik_56
Javascript
C++
/* A recursive C++ program to find Grundy Number for
a game which is one-pile version of Nim.
Game Description : The game starts with a pile of
n stones, and the player to move may take any
positive number of stones up to 3 only.
The last player to move wins. */
#include
using namespace std;
// A Function to calculate Mex of all the values in
// that set.
// A function to Compute Grundy Number of 'n'
// Only this function varies according to the game
int calculateGrundy(int n)
{
if (n == 0)
return (0);
if (n == 1)
return (1);
if (n == 2)
return (2);
if (n == 3)
return (3);
else
return (n%(3+1));
}
// Driver program to test above functions
int main()
{
int n = 10;
printf("%d", calculateGrundy(n));
return (0);
}
Java
/* A recursive Java program to find
Grundy Number for a game which is
one-pile version of Nim.
Game Description : The game starts with
a pile of n stones, and the player to
move may take any positive number of stones
up to 3 only.The last player to move wins. */
import java.util.*;
class GFG
{
// A function to Compute Grundy
// Number of 'n' Only this function
// varies according to the game
static int calculateGrundy(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
if (n == 2)
return 2;
if (n == 3)
return 3;
else
return (n%(3+1));
}
// Driver code
public static void main(String[] args)
{
int n = 10;
System.out.printf("%d", calculateGrundy(n));
}
}
// This code is contributed by rahulnamdevrn27
Python3
# A recursive Python3 program to find Grundy Number
# for a game which is one-pile version of Nim.
# Game Description : The game starts with a pile
# of n stones, and the player to move may take
# any positive number of stones up to 3 only.
# The last player to move wins.
# A function to Compute Grundy Number of 'n'
# Only this function varies according to the game
def calculateGrundy(n):
if 0 <= n <= 3:
return n
else:
return (n%(3+1));
# Driver program to test above functions
if __name__ == "__main__":
n = 10
print(calculateGrundy(n))
# This code is contributed by rahulnamdevrn27
C#
/* A recursive Java program to find Grundy Number
for a game which is one-pile version of Nim.
Game Description : The game starts with a pile of
n stones, and the player to move may take any
positive number of stones up to 3 only.The last
player to move wins. */
using System;
using System.Collections.Generic;
class GFG
{
// A function to Compute Grundy Number of
// 'n' Only this function varies according
// to the game
static int calculateGrundy(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
if (n == 2)
return 2;
if (n == 3)
return 3;
else
return (n%(3+1));
}
// Driver code
public static void Main(String[] args)
{
int n = 10;
Console.Write(calculateGrundy(n));
}
}
// This code is contributed by rahulnamdevrn27
Javascript
C++
/* A recursive C++ program to find Grundy Number for
a game.
Game Description: The game starts with a number- 'n'
and the player to move divides the number- 'n' with 2, 3
or 6 and then takes the floor. If the integer becomes 0,
it is removed. The last player to move wins. */
#include
using namespace std;
// A Function to calculate Mex of all the values in
// that set.
int calculateMex(unordered_set Set)
{
int Mex = 0;
while (Set.find(Mex) != Set.end())
Mex++;
return (Mex);
}
// A function to Compute Grundy Number of 'n'
// Only this function varies according to the game
int calculateGrundy (int n)
{
if (n == 0)
return (0);
unordered_set Set; // A Hash Table
Set.insert(calculateGrundy(n/2));
Set.insert(calculateGrundy(n/3));
Set.insert(calculateGrundy(n/6));
return (calculateMex(Set));
}
// Driver program to test above functions
int main()
{
int n = 10;
printf("%d", calculateGrundy (n));
return (0);
}
Java
/* A recursive Java program to find Grundy Number for
a game.
Game Description : The game starts with a number- 'n'
and the player to move divides the number- 'n' with 2, 3
or 6 and then takes the floor. If the integer becomes 0,
it is removed. The last player to move wins. */
import java.util.*;
class GFG
{
// A Function to calculate Mex of all the values in
// that set.
static int calculateMex(HashSet Set)
{
int Mex = 0;
while (Set.contains(Mex))
{
Mex++;
}
return (Mex);
}
// A function to Compute Grundy Number of 'n'
// Only this function varies according to the game
static int calculateGrundy(int n)
{
if (n == 0)
{
return (0);
}
HashSet Set = new HashSet(); // A Hash Table
Set.add(calculateGrundy(n / 2));
Set.add(calculateGrundy(n / 3));
Set.add(calculateGrundy(n / 6));
return (calculateMex(Set));
}
// Driver code
public static void main(String[] args)
{
int n = 10;
System.out.printf("%d", calculateGrundy(n));
}
}
// This code is contributed by PrinciRaj1992
Python3
# A recursive Python3 program to
# find Grundy Number for a game.
# Game Description : The game starts with a number- 'n'
# and the player to move divides the number- 'n' with 2, 3
# or 6 and then take the floor. If the integer becomes 0,
# it is removed. The last player to move wins.
# A Function to calculate Mex
# of all the values in that set.
def calculateMex(Set):
Mex = 0
while Mex in Set:
Mex += 1
return Mex
# A function to Compute Grundy Number of 'n'
# Only this function varies according to the game
def calculateGrundy(n):
if n == 0:
return 0
Set = set() # A Hash Table
Set.add(calculateGrundy(n // 2))
Set.add(calculateGrundy(n // 3))
Set.add(calculateGrundy(n // 6))
return (calculateMex(Set))
# Driver program to test above functions
if __name__ == "__main__":
n = 10
print(calculateGrundy(n))
# This code is contributed by Rituraj Jain
C#
/* A recursive C# program to find Grundy Number for
a game.
Game Description: The game starts with a number- 'n'
and the player to move divides the number- 'n' with 2, 3
or 6 and then takes the floor. If the integer becomes 0,
it is removed. The last player to move wins. */
using System;
using System.Collections.Generic;
class GFG
{
// A Function to calculate Mex of
// all the values in that set.
static int calculateMex(HashSet Set)
{
int Mex = 0;
while (Set.Contains(Mex))
{
Mex++;
}
return (Mex);
}
// A function to Compute Grundy Number of 'n'
// Only this function varies according to the game
static int calculateGrundy(int n)
{
if (n == 0)
{
return (0);
}
// A Hash Table
HashSet Set = new HashSet();
Set.Add(calculateGrundy(n / 2));
Set.Add(calculateGrundy(n / 3));
Set.Add(calculateGrundy(n / 6));
return (calculateMex(Set));
}
// Driver code
public static void Main()
{
int n = 10;
Console.WriteLine(calculateGrundy(n));
}
}
// This code is contributed by PrinciRaj1992
Javascript
输出 :
10
由于存在重叠子问题,因此可以使用动态规划优化上述解决方案。可以在此处找到基于动态编程的实现。
示例 2
游戏从一堆 n 块石头开始,要移动的玩家可以拿任意正数的石头,最多只能拿 3 块。最后移动的玩家获胜。哪位玩家赢得了比赛?这个游戏是 Nim 的 1 桩版本。
因为如果第一个玩家有 0 个石头,他会立即输,所以 Grundy(0) = 0
如果玩家有 1 个石头,那么他可以拿走所有的石头并获胜。所以游戏的下一个可能位置(对于其他玩家)是 (0) 颗棋子
因此,Grundy(1) = Mex(0) = 1 [根据 Mex 的定义]
同样,如果玩家有 2 个石头,那么他可以只拿 1 个石头,也可以拿 2 个石头并获胜。所以游戏的下一个可能位置(对于另一个玩家)分别是 (1, 0) 颗棋子。
因此,Grundy(2) = Mex(0, 1) = 2 [根据 Mex 的定义]
同理,Grundy(3) = Mex(0, 1, 2) = 3 【根据Mex的定义】
但是 4 块石头呢?
如果玩家有 4 块石头,那么他可以拿 1 块石头,也可以拿 2 块石头或 3 块石头,但他不能拿 4 块石头(见游戏限制)。所以游戏的下一个可能位置(对于另一个玩家)分别是 (3, 2, 1) 颗棋子。
因此,Grundy(4) = Mex (1, 2, 3) = 0 [根据 Mex 的定义]
因此,我们可以递归地将任何 n >= 4 的 Grundy Number 定义为-
Grundy(n) = Mex[Grundy (n-1), Grundy (n-2), Grundy (n-3)]
我们总结了下表中从 0 到 10 的第一个 Grundy 值-
C++
/* A recursive C++ program to find Grundy Number for
a game which is one-pile version of Nim.
Game Description : The game starts with a pile of
n stones, and the player to move may take any
positive number of stones up to 3 only.
The last player to move wins. */
#include
using namespace std;
// A Function to calculate Mex of all the values in
// that set.
// A function to Compute Grundy Number of 'n'
// Only this function varies according to the game
int calculateGrundy(int n)
{
if (n == 0)
return (0);
if (n == 1)
return (1);
if (n == 2)
return (2);
if (n == 3)
return (3);
else
return (n%(3+1));
}
// Driver program to test above functions
int main()
{
int n = 10;
printf("%d", calculateGrundy(n));
return (0);
}
Java
/* A recursive Java program to find
Grundy Number for a game which is
one-pile version of Nim.
Game Description : The game starts with
a pile of n stones, and the player to
move may take any positive number of stones
up to 3 only.The last player to move wins. */
import java.util.*;
class GFG
{
// A function to Compute Grundy
// Number of 'n' Only this function
// varies according to the game
static int calculateGrundy(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
if (n == 2)
return 2;
if (n == 3)
return 3;
else
return (n%(3+1));
}
// Driver code
public static void main(String[] args)
{
int n = 10;
System.out.printf("%d", calculateGrundy(n));
}
}
// This code is contributed by rahulnamdevrn27
蟒蛇3
# A recursive Python3 program to find Grundy Number
# for a game which is one-pile version of Nim.
# Game Description : The game starts with a pile
# of n stones, and the player to move may take
# any positive number of stones up to 3 only.
# The last player to move wins.
# A function to Compute Grundy Number of 'n'
# Only this function varies according to the game
def calculateGrundy(n):
if 0 <= n <= 3:
return n
else:
return (n%(3+1));
# Driver program to test above functions
if __name__ == "__main__":
n = 10
print(calculateGrundy(n))
# This code is contributed by rahulnamdevrn27
C#
/* A recursive Java program to find Grundy Number
for a game which is one-pile version of Nim.
Game Description : The game starts with a pile of
n stones, and the player to move may take any
positive number of stones up to 3 only.The last
player to move wins. */
using System;
using System.Collections.Generic;
class GFG
{
// A function to Compute Grundy Number of
// 'n' Only this function varies according
// to the game
static int calculateGrundy(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
if (n == 2)
return 2;
if (n == 3)
return 3;
else
return (n%(3+1));
}
// Driver code
public static void Main(String[] args)
{
int n = 10;
Console.Write(calculateGrundy(n));
}
}
// This code is contributed by rahulnamdevrn27
Javascript
输出 :
2
当我们可以捡起 k 个石头时,上面代码的一般解决方案可以在这里找到。
示例 3
游戏以数字-‘n’ 开始,移动的玩家将数字-‘n’ 除以2、3 或6,然后进入地板。如果整数变为 0,则将其删除。最后移动的玩家获胜。哪位玩家赢得了比赛?
我们总结了下表中从 0 到 10 的第一个 Grundy 值:
想想我们是如何生成这张表的。
C++
/* A recursive C++ program to find Grundy Number for
a game.
Game Description: The game starts with a number- 'n'
and the player to move divides the number- 'n' with 2, 3
or 6 and then takes the floor. If the integer becomes 0,
it is removed. The last player to move wins. */
#include
using namespace std;
// A Function to calculate Mex of all the values in
// that set.
int calculateMex(unordered_set Set)
{
int Mex = 0;
while (Set.find(Mex) != Set.end())
Mex++;
return (Mex);
}
// A function to Compute Grundy Number of 'n'
// Only this function varies according to the game
int calculateGrundy (int n)
{
if (n == 0)
return (0);
unordered_set Set; // A Hash Table
Set.insert(calculateGrundy(n/2));
Set.insert(calculateGrundy(n/3));
Set.insert(calculateGrundy(n/6));
return (calculateMex(Set));
}
// Driver program to test above functions
int main()
{
int n = 10;
printf("%d", calculateGrundy (n));
return (0);
}
Java
/* A recursive Java program to find Grundy Number for
a game.
Game Description : The game starts with a number- 'n'
and the player to move divides the number- 'n' with 2, 3
or 6 and then takes the floor. If the integer becomes 0,
it is removed. The last player to move wins. */
import java.util.*;
class GFG
{
// A Function to calculate Mex of all the values in
// that set.
static int calculateMex(HashSet Set)
{
int Mex = 0;
while (Set.contains(Mex))
{
Mex++;
}
return (Mex);
}
// A function to Compute Grundy Number of 'n'
// Only this function varies according to the game
static int calculateGrundy(int n)
{
if (n == 0)
{
return (0);
}
HashSet Set = new HashSet(); // A Hash Table
Set.add(calculateGrundy(n / 2));
Set.add(calculateGrundy(n / 3));
Set.add(calculateGrundy(n / 6));
return (calculateMex(Set));
}
// Driver code
public static void main(String[] args)
{
int n = 10;
System.out.printf("%d", calculateGrundy(n));
}
}
// This code is contributed by PrinciRaj1992
蟒蛇3
# A recursive Python3 program to
# find Grundy Number for a game.
# Game Description : The game starts with a number- 'n'
# and the player to move divides the number- 'n' with 2, 3
# or 6 and then take the floor. If the integer becomes 0,
# it is removed. The last player to move wins.
# A Function to calculate Mex
# of all the values in that set.
def calculateMex(Set):
Mex = 0
while Mex in Set:
Mex += 1
return Mex
# A function to Compute Grundy Number of 'n'
# Only this function varies according to the game
def calculateGrundy(n):
if n == 0:
return 0
Set = set() # A Hash Table
Set.add(calculateGrundy(n // 2))
Set.add(calculateGrundy(n // 3))
Set.add(calculateGrundy(n // 6))
return (calculateMex(Set))
# Driver program to test above functions
if __name__ == "__main__":
n = 10
print(calculateGrundy(n))
# This code is contributed by Rituraj Jain
C#
/* A recursive C# program to find Grundy Number for
a game.
Game Description: The game starts with a number- 'n'
and the player to move divides the number- 'n' with 2, 3
or 6 and then takes the floor. If the integer becomes 0,
it is removed. The last player to move wins. */
using System;
using System.Collections.Generic;
class GFG
{
// A Function to calculate Mex of
// all the values in that set.
static int calculateMex(HashSet Set)
{
int Mex = 0;
while (Set.Contains(Mex))
{
Mex++;
}
return (Mex);
}
// A function to Compute Grundy Number of 'n'
// Only this function varies according to the game
static int calculateGrundy(int n)
{
if (n == 0)
{
return (0);
}
// A Hash Table
HashSet Set = new HashSet();
Set.Add(calculateGrundy(n / 2));
Set.Add(calculateGrundy(n / 3));
Set.Add(calculateGrundy(n / 6));
return (calculateMex(Set));
}
// Driver code
public static void Main()
{
int n = 10;
Console.WriteLine(calculateGrundy(n));
}
}
// This code is contributed by PrinciRaj1992
Javascript
输出 :
0
由于存在重叠子问题,因此可以使用动态规划优化上述解决方案。可以在此处找到基于动态编程的实现。
如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程和学生竞争性编程现场课程。