組み合わせゲーム理論|セット4(Sprague -Grundy定理)

組み合わせゲーム理論|セット4(Sprague -Grundy定理)

前提条件: 不機嫌な数字/数とメックス
セット2(https://www.geeksforgeeks.org/dsa/combinatorial-game-theory-set-2-game-nim/)ですでに見てきました。
古典的なNIMゲームを少し変更したとします。今回は、各プレイヤーは1つまたは3つの石のみを削除できます(NIMの古典的なゲームのように石の数は何もありません)。誰が勝つかを予測できますか?
はい、Sprague-Grundy定理を使用して勝者を予測できます。

Sprague-Grundy定理とは何ですか?  
Nサブゲームと2人のプレイヤーAとBで構成される複合ゲーム(複数のサブゲーム)があるとします。その後、Sprague-Grundy定理は、AとBの両方が最適に再生された場合(つまり、間違いを犯さない)、最初に開始するプレイヤーは、ゲームの最初のサブゲムでの地位のXorが勝つことが保証されます。それ以外の場合は、XORがゼロに評価された場合、プレーヤーAは何があっても間違いなく失われます。

Sprague Grundy定理を適用する方法は?  
Sprague-Grundy定理を任意のものに適用できます 公平なゲーム そしてそれを解決します。基本的な手順は次のようにリストされています。 

  1. 複合ゲームをサブゲームに分割します。
  2. 次に、サブゲームごとに、その位置でグランディ数を計算します。
  3. 次に、計算されたすべてのグランディ数のXORを計算します。
  4. XOR値がゼロ以外の場合、ターン(最初のプレーヤー)を獲得しようとしているプレーヤーは、何があっても失う運命にあることになります。

ゲームの例: ゲームは3つの4つの石と5つの石を持つ3つの山から始まり、プレーヤーは移動する可能性があります。最後に移動するプレーヤーが勝ちます。両方のプレイヤーが最適にプレイすると仮定して、どのプレーヤーがゲームに勝ちますか?

Sprague-Grundy定理を適用することで誰が勝つかを知る方法は?  
このゲーム自体がいくつかのサブゲームで構成されていることがわかります。 
最初のステップ: サブゲームは、各山と見なすことができます。 
2番目のステップ: 下の表からそれがわかります 

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

Sprague -Grundy定理

このゲームの不機嫌な数を計算する方法をすでに見ています 前の 記事。
3番目のステップ: 3 0 1 = 2のXOR
4番目のステップ: XORはゼロ以外の数字なので、最初のプレーヤーが勝つと言えます。

以下は、4つ以上のステップを実装するプログラムです。 

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>

出力:  

Player 1 will win 

時間の複雑さ: o(n^2)ここで、nは山の最大石の数です。 

スペースの複雑さ: o(n)グルンディアレイを使用してサブ問題の結果を保存して冗長計算を避け、o(n)スペースが必要です。

参考文献:  
https://en.wikipedia.org/wiki/sprague%E2%80%93Grundy_Theorem

読者への運動: 以下のゲームを検討してください。 
ゲームは、N整数A1 A2を持つ2人のプレイヤーによってプレイされます。彼/彼女のターンでは、プレーヤーが整数を選択して、整数を2 3または6で分割し、床に行きます。整数が0になると削除されます。最後に移動するプレーヤーが勝ちます。両方のプレイヤーが最適にプレイした場合、どのプレーヤーがゲームに勝ちますか?
ヒント:の例3を参照してください 前の 記事。