📜  组合博弈论 | Set 3 (Grundy Numbers/Numbers and Mex)

📅  最后修改于: 2022-05-13 01:56:07.955000             🧑  作者: Mango

组合博弈论 | Set 3 (Grundy Numbers/Numbers and Mex)

我们在第 1 集中介绍了组合博弈论,并在第 2 集中讨论了 Nim 博弈。
Grundy Number 是一个定义游戏状态的数字。我们可以根据 Grundy Number 来定义任何不偏不倚的游戏(例如:nim 游戏)。

一旦我们使用 Sprague-Grundy 定理计算了与该游戏相关的 Grundy 数,Grundy 数或数决定了如何解决任何公正游戏(不仅是 Nim 游戏)。
但在计算 Grundy Numbers 之前,我们需要了解另一个术语——Mex。

什么是墨西哥?
一组数字的“最小排除项”又名“Mex”是该集合中不存在的最小非负数。

墨西哥

如何计算格兰迪数?
我们使用这个定义 - 对于第一个玩家立即输掉的游戏,Grundy Number/ 数字等于 0,并且等于任何其他游戏的所有可能下一个位置的数字的 Mex。
下面是三个示例游戏和程序,用于计算每个游戏的 Grundy Number 和 Mex。 Grundy 数的计算基本上是由一个称为 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 值-

格兰迪1

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 数:
Grundy(n) = Mex[Grundy (n-1), Grundy (n-2), Grundy (n-3)]

我们在下表中总结了第一个从 0 到 10 的 Grundy 值-

格兰迪2

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


输出 :

2

当我们被允许拾取 k 块石头时,上述代码的通用解决方案可以在这里找到。

示例 3
游戏以数字“n”开始,移动的玩家将数字“n”除以 2、3 或 6,然后发言。如果整数变为 0,则将其删除。最后移动的玩家获胜。哪位玩家赢得比赛?

我们在下表中总结了第一个从 0 到 10 的 Grundy 值:

格兰迪3

想想我们是如何生成这个表的。

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


输出 :

0

上述解决方案可以使用动态规划进行优化,因为存在重叠的子问题。可以在此处找到基于动态编程的实现。