Teoría de juegos combinatorios | Conjunto 4 (Sprague – Teorema de Grundy)

Requisitos previos: Grundy Numbers/Numbers y Mex
Ya vimos en el Set 2 (https://www.geeksforgeeks.org/combinatorial-game-theory-set-2-game-nim/), que podemos encontrar quién gana en un juego de Nim sin realmente jugar el juego.
Supongamos que cambiamos un poco el clásico juego de Nim. Esta vez cada jugador solo puede quitar 1, 2 o 3 piedras (y no cualquier número de piedras como en el clásico juego de Nim). ¿Podemos predecir quién ganará?
Sí, podemos predecir el ganador usando el Teorema de Sprague-Grundy.

¿Qué es el teorema de Sprague-Grundy?  
Supongamos que hay un juego compuesto (más de un subjuego) formado por N subjuegos y dos jugadores, A y B. Entonces el teorema de Sprague-Grundy dice que si tanto A como B juegan de manera óptima (es decir, no comete ningún error), entonces el jugador que comienza primero tiene la garantía de ganar si el XOR de los números grundy de posición en cada subjuego al comienzo del juego no es cero. De lo contrario, si el XOR se evalúa como cero, entonces el jugador A definitivamente perderá, pase lo que pase.

¿Cómo aplicar el Teorema de Sprague Grundy?  
Podemos aplicar el Teorema de Sprague-Grundy en cualquier juego imparcial y resolverlo. Los pasos básicos se enumeran a continuación: 

  1. Divide el juego compuesto en subjuegos.
  2. Luego, para cada subjuego, calcule el número de Grundy en esa posición.
  3. Luego calcule el XOR de todos los números de Grundy calculados.
  4. Si el valor de XOR es distinto de cero, entonces el jugador que va a hacer el turno (primer jugador) ganará, de lo contrario, está destinado a perder, pase lo que pase.

Juego de ejemplo: El juego comienza con 3 pilas que tienen 3, 4 y 5 piedras, y el jugador que mueve puede tomar cualquier número positivo de piedras hasta 3 solo de cualquiera de las pilas [siempre que la pila tenga esa cantidad de piedras]. El último jugador en moverse gana. ¿Qué jugador gana el juego suponiendo que ambos jugadores juegan de manera óptima?

¿Cómo saber quién ganará aplicando el Teorema de Sprague-Grundy?  
Como podemos ver, este juego se compone de varios subjuegos. 
Primer paso: los subjuegos se pueden considerar como montones. 
Segundo paso: Vemos en la siguiente tabla que 

Grundy(3) = 3
Grundy(4) = 0 
Grundy(5) = 1 

Sprague - Grundy Theorem

Ya hemos visto cómo calcular los Números Grundy de este juego en el artículo anterior .
Tercer paso: el XOR de 3, 0, 1 = 2
Cuarto paso: dado que XOR es un número distinto de cero, podemos decir que el primer jugador ganará.

A continuación se muestra el programa que implementa los 4 pasos anteriores. 

C++

/*  Game Description-
 "A game is played between two players and there are N piles
 of stones such that each pile has certain number of stones.
 On his/her turn, a player selects a pile and can take any
 non-zero number of stones upto 3 (i.e- 1,2,3)
 The player who cannot move is considered to lose the game
 (i.e., one who take the last stone is the winner).
 Can you find which player wins the game if both players play
 optimally (they don't make any mistake)? "
 
 A Dynamic Programming approach to calculate Grundy Number
 and Mex and find the Winner using Sprague - Grundy Theorem. */
 
#include<bits/stdc++.h>
using namespace std;
 
/* piles[] -> Array having the initial count of stones/coins
            in each piles before the game has started.
   n       -> Number of piles
 
   Grundy[] -> Array having the Grundy Number corresponding to
             the initial position of each piles in the game
 
   The piles[] and Grundy[] are having 0-based indexing*/
 
#define PLAYER1 1
#define PLAYER2 2
 
// A Function to calculate Mex of all the values in that set
int calculateMex(unordered_set<int> Set)
{
    int Mex = 0;
 
    while (Set.find(Mex) != Set.end())
        Mex++;
 
    return (Mex);
}
 
// A function to Compute Grundy Number of 'n'
int calculateGrundy(int n, int Grundy[])
{
    Grundy[0] = 0;
    Grundy[1] = 1;
    Grundy[2] = 2;
    Grundy[3] = 3;
 
    if (Grundy[n] != -1)
        return (Grundy[n]);
 
    unordered_set<int> Set; // A Hash Table
 
    for (int i=1; i<=3; i++)
            Set.insert (calculateGrundy (n-i, Grundy));
 
    // Store the result
    Grundy[n] = calculateMex (Set);
 
    return (Grundy[n]);
}
 
// A function to declare the winner of the game
void declareWinner(int whoseTurn, int piles[],
                    int Grundy[], int n)
{
    int xorValue = Grundy[piles[0]];
 
    for (int i=1; i<=n-1; i++)
        xorValue = xorValue ^ Grundy[piles[i]];
 
    if (xorValue != 0)
    {
        if (whoseTurn == PLAYER1)
            printf("Player 1 will win\n");
        else
            printf("Player 2 will win\n");
    }
    else
    {
        if (whoseTurn == PLAYER1)
            printf("Player 2 will win\n");
        else
            printf("Player 1 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]);
 
    // Find the maximum element
    int maximum = *max_element(piles, piles + n);
 
    // An array to cache the sub-problems so that
    // re-computation of same sub-problems is avoided
    int Grundy[maximum + 1];
    memset(Grundy, -1, sizeof (Grundy));
 
    // Calculate Grundy Value of piles[i] and store it
    for (int i=0; i<=n-1; i++)
        calculateGrundy(piles[i], Grundy);
 
    declareWinner(PLAYER1, piles, Grundy, n);
 
    /* Test Case 2
    int piles[] = {3, 8, 2};
    int n = sizeof(piles)/sizeof(piles[0]);
 
 
    int maximum = *max_element (piles, piles + n);
 
    // An array to cache the sub-problems so that
    // re-computation of same sub-problems is avoided
    int Grundy [maximum + 1];
    memset(Grundy, -1, sizeof (Grundy));
 
    // Calculate Grundy Value of piles[i] and store it
    for (int i=0; i<=n-1; i++)
        calculateGrundy(piles[i], Grundy);
 
    declareWinner(PLAYER2, piles, Grundy, n);   */
 
    return (0);
}

Java

import java.util.*;
 
/* Game Description-
"A game is played between two players and there are N piles
of stones such that each pile has certain number of stones.
On his/her turn, a player selects a pile and can take any
non-zero number of stones upto 3 (i.e- 1,2,3)
The player who cannot move is considered to lose the game
(i.e., one who take the last stone is the winner).
Can you find which player wins the game if both players play
optimally (they don't make any mistake)? "
 
A Dynamic Programming approach to calculate Grundy Number
and Mex and find the Winner using Sprague - Grundy Theorem. */
 
class GFG {
     
 
/* piles[] -> Array having the initial count of stones/coins
            in each piles before the game has started.
n     -> Number of piles
 
Grundy[] -> Array having the Grundy Number corresponding to
            the initial position of each piles in the game
 
The piles[] and Grundy[] are having 0-based indexing*/
 
static int PLAYER1 = 1;
static int PLAYER2 = 2;
 
// A Function to calculate Mex of all the values in that set
static int calculateMex(HashSet<Integer> Set)
{
    int Mex = 0;
 
    while (Set.contains(Mex))
        Mex++;
 
    return (Mex);
}
 
// A function to Compute Grundy Number of 'n'
static int calculateGrundy(int n, int Grundy[])
{
    Grundy[0] = 0;
    Grundy[1] = 1;
    Grundy[2] = 2;
    Grundy[3] = 3;
 
    if (Grundy[n] != -1)
        return (Grundy[n]);
 
    // A Hash Table
    HashSet<Integer> Set = new HashSet<Integer>();
 
    for (int i = 1; i <= 3; i++)
            Set.add(calculateGrundy (n - i, Grundy));
 
    // Store the result
    Grundy[n] = calculateMex (Set);
 
    return (Grundy[n]);
}
 
// A function to declare the winner of the game
static void declareWinner(int whoseTurn, int piles[],
                    int Grundy[], int n)
{
    int xorValue = Grundy[piles[0]];
 
    for (int i = 1; i <= n - 1; i++)
        xorValue = xorValue ^ Grundy[piles[i]];
 
    if (xorValue != 0)
    {
        if (whoseTurn == PLAYER1)
            System.out.printf("Player 1 will win\n");
        else
            System.out.printf("Player 2 will win\n");
    }
    else
    {
        if (whoseTurn == PLAYER1)
            System.out.printf("Player 2 will win\n");
        else
            System.out.printf("Player 1 will win\n");
    }
 
    return;
}
 
 
// Driver code
public static void main(String[] args)
{
     
    // Test Case 1
    int piles[] = {3, 4, 5};
    int n = piles.length;
 
    // Find the maximum element
    int maximum = Arrays.stream(piles).max().getAsInt();
 
    // An array to cache the sub-problems so that
    // re-computation of same sub-problems is avoided
    int Grundy[] = new int[maximum + 1];
    Arrays.fill(Grundy, -1);
 
    // Calculate Grundy Value of piles[i] and store it
    for (int i = 0; i <= n - 1; i++)
        calculateGrundy(piles[i], Grundy);
 
    declareWinner(PLAYER1, piles, Grundy, n);
 
    /* Test Case 2
    int piles[] = {3, 8, 2};
    int n = sizeof(piles)/sizeof(piles[0]);
 
 
    int maximum = *max_element (piles, piles + n);
 
    // An array to cache the sub-problems so that
    // re-computation of same sub-problems is avoided
    int Grundy [maximum + 1];
    memset(Grundy, -1, sizeof (Grundy));
 
    // Calculate Grundy Value of piles[i] and store it
    for (int i=0; i<=n-1; i++)
        calculateGrundy(piles[i], Grundy);
 
    declareWinner(PLAYER2, piles, Grundy, n); */
 
    }
}
 
// This code is contributed by PrinciRaj1992

Python3

'''  Game Description-
 "A game is played between two players and there are N piles
 of stones such that each pile has certain number of stones.
 On his/her turn, a player selects a pile and can take any
 non-zero number of stones upto 3 (i.e- 1,2,3)
 The player who cannot move is considered to lose the game
 (i.e., one who take the last stone is the winner).
 Can you find which player wins the game if both players play
 optimally (they don't make any mistake)? "
   
 A Dynamic Programming approach to calculate Grundy Number
 and Mex and find the Winner using Sprague - Grundy Theorem.
  
     piles[] -> Array having the initial count of stones/coins
            in each piles before the game has started.
   n       -> Number of piles
   
   Grundy[] -> Array having the Grundy Number corresponding to
             the initial position of each piles in the game
   
   The piles[] and Grundy[] are having 0-based indexing'''
 
PLAYER1 = 1
PLAYER2 = 2  
 
# 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'
def calculateGrundy(n, Grundy):
 
    Grundy[0] = 0
    Grundy[1] = 1
    Grundy[2] = 2
    Grundy[3] = 3
   
    if (Grundy[n] != -1):
        return (Grundy[n])
     
    # A Hash Table
    Set = set()
   
    for i in range(1, 4):
        Set.add(calculateGrundy(n - i,
                                Grundy))
     
    # Store the result
    Grundy[n] = calculateMex(Set)
   
    return (Grundy[n])
  
# A function to declare the winner of the game
def declareWinner(whoseTurn, piles, Grundy, n):
 
    xorValue = Grundy[piles[0]];
   
    for i in range(1, n):
        xorValue = (xorValue ^
                    Grundy[piles[i]])
   
    if (xorValue != 0):
     
        if (whoseTurn == PLAYER1):
            print("Player 1 will win\n");
        else:
            print("Player 2 will win\n");
    else:
     
        if (whoseTurn == PLAYER1):
            print("Player 2 will win\n");
        else:
            print("Player 1 will win\n");
     
# Driver code
if __name__=="__main__":
     
    # Test Case 1
    piles = [ 3, 4, 5 ]
    n = len(piles)
   
    # Find the maximum element
    maximum = max(piles)
   
    # An array to cache the sub-problems so that
    # re-computation of same sub-problems is avoided
    Grundy = [-1 for i in range(maximum + 1)];
   
    # Calculate Grundy Value of piles[i] and store it
    for i in range(n):
        calculateGrundy(piles[i], Grundy);
   
    declareWinner(PLAYER1, piles, Grundy, n);
   
    ''' Test Case 2
    int piles[] = {3, 8, 2};
    int n = sizeof(piles)/sizeof(piles[0]);
   
   
    int maximum = *max_element (piles, piles + n);
   
    // An array to cache the sub-problems so that
    // re-computation of same sub-problems is avoided
    int Grundy [maximum + 1];
    memset(Grundy, -1, sizeof (Grundy));
   
    // Calculate Grundy Value of piles[i] and store it
    for (int i=0; i<=n-1; i++)
        calculateGrundy(piles[i], Grundy);
   
    declareWinner(PLAYER2, piles, Grundy, n);   '''
 
# This code is contributed by rutvik_56

C#

using System;
using System.Linq;
using System.Collections.Generic;
 
/* Game Description-
"A game is played between two players and there are N piles
of stones such that each pile has certain number of stones.
On his/her turn, a player selects a pile and can take any
non-zero number of stones upto 3 (i.e- 1,2,3)
The player who cannot move is considered to lose the game
(i.e., one who take the last stone is the winner).
Can you find which player wins the game if both players play
optimally (they don't make any mistake)? "
 
A Dynamic Programming approach to calculate Grundy Number
and Mex and find the Winner using Sprague - Grundy Theorem. */
 
class GFG
{
     
 
/* piles[] -> Array having the initial count of stones/coins
            in each piles before the game has started.
n -> Number of piles
 
Grundy[] -> Array having the Grundy Number corresponding to
            the initial position of each piles in the game
 
The piles[] and Grundy[] are having 0-based indexing*/
 
static int PLAYER1 = 1;
//static int PLAYER2 = 2;
 
// A Function to calculate Mex of all the values in that set
static int calculateMex(HashSet<int> Set)
{
    int Mex = 0;
 
    while (Set.Contains(Mex))
        Mex++;
 
    return (Mex);
}
 
// A function to Compute Grundy Number of 'n'
static int calculateGrundy(int n, int []Grundy)
{
    Grundy[0] = 0;
    Grundy[1] = 1;
    Grundy[2] = 2;
    Grundy[3] = 3;
 
    if (Grundy[n] != -1)
        return (Grundy[n]);
 
    // A Hash Table
    HashSet<int> Set = new HashSet<int>();
 
    for (int i = 1; i <= 3; i++)
            Set.Add(calculateGrundy (n - i, Grundy));
 
    // Store the result
    Grundy[n] = calculateMex (Set);
 
    return (Grundy[n]);
}
 
// A function to declare the winner of the game
static void declareWinner(int whoseTurn, int []piles,
                    int []Grundy, int n)
{
    int xorValue = Grundy[piles[0]];
 
    for (int i = 1; i <= n - 1; i++)
        xorValue = xorValue ^ Grundy[piles[i]];
 
    if (xorValue != 0)
    {
        if (whoseTurn == PLAYER1)
            Console.Write("Player 1 will win\n");
        else
            Console.Write("Player 2 will win\n");
    }
    else
    {
        if (whoseTurn == PLAYER1)
            Console.Write("Player 2 will win\n");
        else
            Console.Write("Player 1 will win\n");
    }
 
    return;
}
 
 
// Driver code
static void Main()
{
     
    // Test Case 1
    int []piles = {3, 4, 5};
    int n = piles.Length;
 
    // Find the maximum element
    int maximum = piles.Max();
 
    // An array to cache the sub-problems so that
    // re-computation of same sub-problems is avoided
    int []Grundy = new int[maximum + 1];
    Array.Fill(Grundy, -1);
 
    // Calculate Grundy Value of piles[i] and store it
    for (int i = 0; i <= n - 1; i++)
        calculateGrundy(piles[i], Grundy);
 
    declareWinner(PLAYER1, piles, Grundy, n);
     
    /* Test Case 2
    int piles[] = {3, 8, 2};
    int n = sizeof(piles)/sizeof(piles[0]);
 
 
    int maximum = *max_element (piles, piles + n);
 
    // An array to cache the sub-problems so that
    // re-computation of same sub-problems is avoided
    int Grundy [maximum + 1];
    memset(Grundy, -1, sizeof (Grundy));
 
    // Calculate Grundy Value of piles[i] and store it
    for (int i=0; i<=n-1; i++)
        calculateGrundy(piles[i], Grundy);
 
    declareWinner(PLAYER2, piles, Grundy, n); */
 
    }
}
 
// This code is contributed by mits

Javascript

<script>
 
/* Game Description-
"A game is played between two players and there are N piles
of stones such that each pile has certain number of stones.
On his/her turn, a player selects a pile and can take any
non-zero number of stones upto 3 (i.e- 1,2,3)
The player who cannot move is considered to lose the game
(i.e., one who take the last stone is the winner).
Can you find which player wins the game if both players play
optimally (they don't make any mistake)? "
  
A Dynamic Programming approach to calculate Grundy Number
and Mex and find the Winner using Sprague - Grundy Theorem. */
 
 
/* piles[] -> Array having the initial count of stones/coins
            in each piles before the game has started.
n     -> Number of piles
  
Grundy[] -> Array having the Grundy Number corresponding to
            the initial position of each piles in the game
  
The piles[] and Grundy[] are having 0-based indexing*/
let PLAYER1 = 1;
let PLAYER2 = 2;
 
// A Function to calculate Mex of all the values in that set
function calculateMex(Set)
{
    let Mex = 0;
  
    while (Set.has(Mex))
        Mex++;
  
    return (Mex);
}
 
// A function to Compute Grundy Number of 'n'
function calculateGrundy(n,Grundy)
{
    Grundy[0] = 0;
    Grundy[1] = 1;
    Grundy[2] = 2;
    Grundy[3] = 3;
  
    if (Grundy[n] != -1)
        return (Grundy[n]);
  
    // A Hash Table
    let Set = new Set();
  
    for (let i = 1; i <= 3; i++)
            Set.add(calculateGrundy (n - i, Grundy));
  
    // Store the result
    Grundy[n] = calculateMex (Set);
  
    return (Grundy[n]);
}
 
// A function to declare the winner of the game
function declareWinner(whoseTurn,piles,Grundy,n)
{
    let xorValue = Grundy[piles[0]];
  
    for (let i = 1; i <= n - 1; i++)
        xorValue = xorValue ^ Grundy[piles[i]];
  
    if (xorValue != 0)
    {
        if (whoseTurn == PLAYER1)
            document.write("Player 1 will win<br>");
        else
            document.write("Player 2 will win<br>");
    }
    else
    {
        if (whoseTurn == PLAYER1)
            document.write("Player 2 will win<br>");
        else
            document.write("Player 1 will win<br>");
    }
  
    return;
}
 
// Driver code
 
// Test Case 1
    let piles = [3, 4, 5];
    let n = piles.length;
      
     
    // Find the maximum element
    let maximum = Math.max(...piles)
  
    // An array to cache the sub-problems so that
    // re-computation of same sub-problems is avoided
    let Grundy = new Array(maximum + 1);
    for(let i=0;i<maximum+1;i++)
        Grundy[i]=0;
  
    // Calculate Grundy Value of piles[i] and store it
    for (let i = 0; i <= n - 1; i++)
        calculateGrundy(piles[i], Grundy);
  
    declareWinner(PLAYER1, piles, Grundy, n);
  
    /* Test Case 2
    int piles[] = {3, 8, 2};
    int n = sizeof(piles)/sizeof(piles[0]);
  
  
    int maximum = *max_element (piles, piles + n);
  
    // An array to cache the sub-problems so that
    // re-computation of same sub-problems is avoided
    int Grundy [maximum + 1];
    memset(Grundy, -1, sizeof (Grundy));
  
    // Calculate Grundy Value of piles[i] and store it
    for (int i=0; i<=n-1; i++)
        calculateGrundy(piles[i], Grundy);
  
    declareWinner(PLAYER2, piles, Grundy, n); */
 
// This code is contributed by avanitrachhadiya2155
</script>

Producción : 

Player 1 will win

Referencias:  
https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_theorem

Ejercicio para los lectores: Considere el siguiente juego. 
“Un juego es jugado por dos jugadores con N números enteros A1, A2, .., AN. En su turno, un jugador selecciona un número entero, lo divide por 2, 3 o 6 y luego toma la palabra. Si el entero se convierte en 0, se elimina. El último jugador en moverse gana. ¿Qué jugador gana el juego si ambos jugadores juegan de manera óptima?
Sugerencia: consulte el ejemplo 3 del artículo anterior .

Este artículo es una contribución de Rachit Belwariar . Si le gusta GeeksforGeeks y le gustaría contribuir, también puede escribir un artículo y enviarlo por correo a review-team@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.
Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.
 

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *