Teorie kombinatoriálních her Set 4 (Sprague - Grundy Teorém)

Teorie kombinatoriálních her Set 4 (Sprague - Grundy Teorém)

Předpoklady: Čísla/čísla Grundy a Mex
Už jsme viděli v sadě 2 (https://www.geeksforgeeks.org/dsa/combinatorial-tera-leory-set-2-rame-nim/), že můžeme najít, kdo vyhraje ve hře Nim, aniž by ve skutečnosti hrál hru.
Předpokládejme, že trochu změníme klasickou hru NIM. Tentokrát může každý hráč odstranit pouze 1 2 nebo 3 kameny (a ne žádný počet kamenů jako v klasické hře NIM). Můžeme předvídat, kdo vyhraje?
Ano, můžeme vítěze předvídat pomocí Sprague-Grundy Teorému.

Co je Sprague-Grundy Teorém?  
Předpokládejme, že existuje složená hra (více než jedna podhra) složená z N sub-her a dvou hráčů A a B. Potom Sprague-Grundy Teorém říká, že pokud A a B hrají optimálně (tj. Neudělují žádné chyby), pak hráč, který se stal prvním, je zaručen, že vyhraje, pokud je vyhrán, pokud je XOR z grundy počtu pozic v každém počátečním hraně. Jinak, pokud XOR vyhodnotí na nulu, pak přehrávač A ztratí rozhodně bez ohledu na to.

Jak aplikovat Sprague Grundy Teorém?  
Můžeme použít Sprague-Grundy Teorém v jakémkoli Nestranná hra a vyřešit to. Základní kroky jsou uvedeny takto: 

  1. Rozbijte kompozitní hru na dílčí hry.
  2. Poté pro každou podhru vypočítejte číslo Grundy v této poloze.
  3. Poté vypočítejte XOR všech vypočtených grundy čísel.
  4. Pokud je hodnota XOR nenulová, pak hráč, který se chystá otočit (první hráč), vyhraje jinak, že je předurčen ke ztrátě bez ohledu na to.

Příklad hra: Hra začíná tím, že 3 hromady mají 3 4 a 5 kamenů a přehrávač se může pohybovat jakýmkoli kladným počtem kamenů až 3 pouze z jakékoli hromady [za předpokladu, že hromada má tolik množství kamenů]. Poslední hráč, který přesune. Který hráč vyhraje hru za předpokladu, že oba hráči hrají optimálně?

Jak zjistit, kdo vyhraje aplikací Sprague-Grundy Teorém?  
Jak vidíme, že tato hra je samo o sobě složena z několika dílčích her. 
První krok: Sub-hry lze považovat za každé hromady. 
Druhý krok: Vidíme z níže uvedené tabulky 

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

Sprague - Grundy Teorém

Už jsme viděli, jak vypočítat Grundyova čísla této hry v předchozí článek.
Třetí krok: Xor 3 0 1 = 2
Čtvrtý krok: Protože XOR je nenulové číslo, takže můžeme říci, že první hráč vyhraje.

Níže je program, který implementuje nad 4 kroky. 

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- 123)    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       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- 123)   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 winn'  );      else      System  .  out  .  printf  (  'Player 2 will winn'  );      }      else      {      if     (  whoseTurn     ==     PLAYER1  )      System  .  out  .  printf  (  'Player 2 will winn'  );      else      System  .  out  .  printf  (  'Player 1 will winn'  );      }      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- 123)     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- 123)   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 winn'  );      else      Console  .  Write  (  'Player 2 will winn'  );      }      else      {      if     (  whoseTurn     ==     PLAYER1  )      Console  .  Write  (  'Player 2 will winn'  );      else      Console  .  Write  (  'Player 1 will winn'  );      }      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- 123)   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  
'
); else document . write ( 'Player 2 will win
'
); } else { if ( whoseTurn == PLAYER1 ) document . write ( 'Player 2 will win
'
); else document . write ( 'Player 1 will win
'
); } 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>

Výstup:  

Player 1 will win 

Složitost času: O (n^2) kde n je maximální počet kamenů v hromadě. 

Složitost prostoru: O (n) Vzhledem k tomu, že se pole Grundy používá k uložení výsledků dílčích problémů, aby se zabránilo nadbytečným výpočtům a vyžaduje O (n) prostor.

Reference:  
https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_Theorem

Cvičení čtenářům: Zvažte níže uvedenou hru. 
Hra hrají dva hráči s N Interges A1 A2 .. An. Na jeho řadě si hráč vybere celé číslo, které ho dělí o 2 3 nebo 6 a poté vezme podlahu. Pokud se celé číslo stane 0, je odstraněno. Poslední hráč, který přesune. Který hráč vyhraje hru, pokud oba hráči hrají optimálně?
Tip: Viz příklad 3 předchozí článek.