Open In App
Related Articles

Predict the winner of the game | Sprague-Grundy

Improve
Improve
Improve
Like Article
Like
Save Article
Save
Report issue
Report

Given a 4×4 binary matrix. Two players A and B are playing a game, at each step a player can select any rectangle with all 1’s in it and replace all 1’s with 0. The player that cannot select any rectangle loses the game. Predict the winner of the game assuming that they both play the game optimally and A starts the game. Examples:

Input : 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 
Output : A Step 1: Player A chooses the rectangle with a single one at position (1, 2), so the new matrix becomes 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 Step 2: Player B chooses the rectangle with a single one at position (1, 3), so the new matrix becomes 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 Step 3: Player A chooses the rectangle with a single one at position (4, 4), so the new matrix becomes 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Step 4: Player B cannot move, hence A wins the game. 

Input : 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 
Output : B

Approach: The problem can be solved using the sprague-grundy theorem. The base case for Sprague-Grundy is Grundy[0] = 0, which is all the positions in the matrix are filled with 0, then B wins it, hence 0. In grundy, recursively we call grundy function with all the states that are possible. The 4×4 matrix can be represented as a binary 16-bit number which is 65535 in int, where every bit represents the position in a matrix. Below are the steps to solve the above problem.

  • Convert the matrix into int val.
  • Call the recursive function with val that generates the grundy value using memoization.
  • Inside the recursive function, all the grundy states can be visited by generating all possible rectangles(using four for loops).
  • Check the generated rectangle, if it is a rectangle of the matrix. Then this is a state to be visited by grundy.
  • To get Grundy value using MEX, please see this.
  • If the recursion return 0, then player B wins, else player A wins.

Below is the implementation of the above approach 

CPP

#include <bits/stdc++.h>
using namespace std;
 
// Gets the max value
int getMex(const unordered_set<int>& s)
{
 int mex = 0;
 while (s.find(mex) != s.end())
  mex++;
 return mex;
}
 
// Find check if the rectangle is a part of the
// the original rectangle
int checkOne(int mat, int i, int j, int k, int l)
{
 
 // initially create the bitset
 // of original intValue
 bitset<16> m(mat);
 
 // Check if it is a part of the rectangle
 for (int x = i; x <= j; x++) {
  for (int y = k; y <= l; y++) {
   int pos = 15 - ((x * 4) + y);
 
   // If not set, then not part
   if (!m.test(pos)) {
    return -1;
   }
   m.reset(pos);
  }
 }
 
 // If part of rectangle
 // then convert to int again and return
 int res = m.to_ullong();
 return res;
}
 
// Recursive function to get the grundy value
int getGrundy(int pos, int grundy[])
{
 
 // If state has been visited
 if (grundy[pos] != -1)
  return grundy[pos];
 
 // For obtaining the MEX value
 unordered_set<int> gSet;
 
 // Generate all the possible rectangles
 for (int i = 0; i <= 3; i++) {
  for (int j = i; j <= 3; j++) {
   for (int k = 0; k <= 3; k++) {
    for (int l = k; l <= 3; l++) {
 
     // check if it is part of the original
     // rectangle, if yes then get the int value
     int res = checkOne(pos, i, j, k, l);
 
     // If it is a part of original matrix
     if (res != -1) {
 
      // Store the grundy value
      // Memorize
      grundy[res] = getGrundy(res, grundy);
 
      // Find MEX
      gSet.insert(grundy[res]);
     }
    }
   }
  }
 }
 
 // Return the MEX
 return getMex(gSet);
}
 
// Convert the matrix to INT
int toInt(int matrix[4][4])
{
 int h = 0;
 
 // Traverse in the matrix
 for (int i = 0; i < 4; ++i)
  for (int j = 0; j < 4; ++j)
   h = 2 * h + matrix[i][j];
 return h;
}
 
// Driver Code
int main()
{
 int mat[4][4] = { { 0, 1, 1, 0 },
     { 0, 0, 0, 0 },
     { 0, 0, 0, 0 },
     { 0, 0, 0, 1 } };
 
 // Get the int value of the matrix
 int intValue = toInt(mat);
 
 int grundy[intValue + 1];
 
 // Initially with -1
 // used for memoization
 memset(grundy, -1, sizeof grundy);
 
 // Base case
 grundy[0] = 0;
 
 // If returned value is non-zero
 if (getGrundy(intValue, grundy))
  cout << "Player A wins";
 else
  cout << "Player B wins";
 
 return 0;
}

                    

Java

import java.util.*;
 
public class Main {
 
    // Gets the max value
    public static int getMex(final HashSet<Integer> s) {
        int mex = 0;
        while (s.contains(mex)) {
            mex++;
        }
        return mex;
    }
 
    // Check if the rectangle is a part of the original rectangle
    public static int checkOne(int mat, int i, int j, int k, int l) {
        // Initially create the bitset of the original intValue
        BitSet m = BitSet.valueOf(new long[]{mat});
 
        // Check if it is a part of the rectangle
        for (int x = i; x <= j; x++) {
            for (int y = k; y <= l; y++) {
                int pos = 15 - ((x * 4) + y);
 
                // If not set, then not part
                if (!m.get(pos)) {
                    return -1;
                }
                m.clear(pos);
            }
        }
 
        // If part of rectangle, then convert to int again and return
        long[] resultArray = m.toLongArray();
        int res = resultArray.length > 0 ? (int) resultArray[0] : 0;
        return res;
    }
 
    // Recursive function to get the grundy value
    public static int getGrundy(int pos, int[] grundy) {
        // If state has been visited
        if (grundy[pos] != -1) {
            return grundy[pos];
        }
 
        // For obtaining the MEX value
        HashSet<Integer> gSet = new HashSet<>();
 
        // Generate all the possible rectangles
        for (int i = 0; i <= 3; i++) {
            for (int j = i; j <= 3; j++) {
                for (int k = 0; k <= 3; k++) {
                    for (int l = k; l <= 3; l++) {
                        // Check if it is part of the original rectangle, if yes, then get the int value
                        int res = checkOne(pos, i, j, k, l);
 
                        // If it is a part of the original matrix
                        if (res != -1) {
                            // Store the grundy value (Memorize)
                            grundy[res] = getGrundy(res, grundy);
 
                            // Find MEX
                            gSet.add(grundy[res]);
                        }
                    }
                }
            }
        }
 
        // Return the MEX
        return getMex(gSet);
    }
 
    // Convert the matrix to INT
    public static int toInt(int[][] matrix) {
        int h = 0;
 
        // Traverse the matrix
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                h = 2 * h + matrix[i][j];
            }
        }
        return h;
    }
 
    // Driver Code
    public static void main(String[] args) {
        int[][] mat = {
                {0, 1, 1, 0},
                {0, 0, 0, 0},
                {0, 0, 0, 0},
                {0, 0, 0, 1}
        };
 
        // Get the int value of the matrix
        int intValue = toInt(mat);
 
        int[] grundy = new int[intValue + 1];
 
        // Initially with -1, used for memoization
        Arrays.fill(grundy, -1);
 
        // Base case
        grundy[0] = 0;
 
        // If returned value is non-zero
        if (getGrundy(intValue, grundy) != 0) {
            System.out.println("Player A wins");
        } else {
            System.out.println("Player B wins");
        }
    }
}

                    

Python3

from typing import List
from collections import defaultdict
from bisect import bisect_left
 
# Gets the max value
 
 
def getMex(s):
    mex = 0
    while mex in s:
        mex += 1
    return mex
 
# Find check if the rectangle is a part of the
# the original rectangle
 
 
def checkOne(mat, i, j, k, l):
    # initially create the bitset
    # of original intValue
    m = format(mat, '016b')
    m = [int(i) for i in m]
 
    # Check if it is a part of the rectangle
    for x in range(i, j+1):
        for y in range(k, l+1):
            pos = 15 - ((x * 4) + y)
 
            # If not set, then not part
            if not m[pos]:
                return -1
            m[pos] = 0
 
    # If part of rectangle
    # then convert to int again and return
    res = int(''.join(map(str, m)), 2)
    return res
 
# Recursive function to get the grundy value
 
 
def getGrundy(pos, grundy):
    # If state has been visited
    if grundy[pos] != -1:
        return grundy[pos]
 
    # For obtaining the MEX value
    gSet = set()
 
    # Generate all the possible rectangles
    for i in range(4):
        for j in range(i, 4):
            for k in range(4):
                for l in range(k, 4):
                    # check if it is part of the original
                    # rectangle, if yes then get the int value
                    res = checkOne(pos, i, j, k, l)
 
                    # If it is a part of original matrix
                    if res != -1:
                        # Store the grundy value
                        # Memorize
                        grundy[res] = getGrundy(res, grundy)
 
                        # Find MEX
                        gSet.add(grundy[res])
 
    # Return the MEX
    return getMex(gSet)
 
# Convert the matrix to INT
 
 
def toInt(matrix):
    h = 0
 
    # Traverse in the matrix
    for i in range(4):
        for j in range(4):
            h = 2 * h + matrix[i][j]
    return h
 
 
# Driver Code
if __name__ == "__main__":
    mat = [[0, 1, 1, 0],
           [0, 0, 0, 0],
           [0, 0, 0, 0],
           [0, 0, 0, 1]]
 
    # Get the int value of the matrix
    intValue = toInt(mat)
 
    grundy = defaultdict(lambda: -1)
 
    # Base case
    grundy[0] = 0
 
    # If returned value is non-zero
    if getGrundy(intValue, grundy):
        print("Player A wins")
    else:
        print("Player B wins")

                    

C#

using System;
using System.Collections.Generic;
using System.Collections;
 
class Program
{
 
  // Gets the max value
  static int getMex(HashSet<int> s)
  {
    int mex = 0;
    while (s.Contains(mex))
      mex++;
    return mex;
  }
 
  // Find check if the rectangle is a part of the
  // the original rectangle
  static int checkOne(int mat, int i, int j, int k, int l)
  {
    // initially create the bitset
    // of original intValue
    var m = new BitArray(new[] { mat });
 
    // Check if it is a part of the rectangle
    for (int x = i; x <= j; x++)
    {
      for (int y = k; y <= l; y++)
      {
        int pos = 15 - ((x * 4) + y);
 
        // If not set, then not part
        if (!m.Get(pos))
        {
          return -1;
        }
        m.Set(pos, false);
      }
    }
 
    // If part of rectangle
    // then convert to int again and return
    var res = new int[1];
    m.CopyTo(res, 0);
    return res[0];
  }
 
  // Recursive function to get the grundy value
  static int getGrundy(int pos, int[] grundy)
  {
    // If state has been visited
    if (grundy[pos] != -1)
      return grundy[pos];
 
    // For obtaining the MEX value
    var gSet = new HashSet<int>();
 
    // Generate all the possible rectangles
    for (int i = 0; i <= 3; i++)
    {
      for (int j = i; j <= 3; j++)
      {
        for (int k = 0; k <= 3; k++)
        {
          for (int l = k; l <= 3; l++)
          {
            // check if it is part of the original
            // rectangle, if yes then get the int value
            int res = checkOne(pos, i, j, k, l);
 
            // If it is a part of original matrix
            if (res != -1)
            {
              // Store the grundy value
              // Memorize
              grundy[res] = getGrundy(res, grundy);
 
              // Find MEX
              gSet.Add(grundy[res]);
            }
          }
        }
      }
    }
    // Return the MEX
    return getMex(gSet);
  }
 
  // Convert the matrix to INT
  static int toInt(int[,] matrix)
  {
    int h = 0;
 
    // Traverse in the matrix
    for (int i = 0; i < 4; ++i)
    {
      for (int j = 0; j < 4; ++j)
      {
        h = 2 * h + matrix[i, j];
      }
    }
    return h;
  }
 
  // Driver Code
  static void Main(string[] args)
  {
    int[,] mat = { { 0, 1, 1, 0 },
                  { 0, 0, 0, 0 },
                  { 0, 0, 0, 0 },
                  { 0, 0, 0, 1 } };
 
    // Get the int value of the matrix
    int intValue = toInt(mat);
    int[] grundy = new int[intValue + 1];
 
    // Initially with -1
    // used for memoization
    for (int i = 0; i < grundy.Length; i++)
    {
      grundy[i] = -1;
    }
    // Base case
    grundy[0] = 0;
 
    // If returned value is non-zero
    if (getGrundy(intValue, grundy) != 0)
    {
      Console.WriteLine("Player A wins");
    }
    else
    {
      Console.WriteLine("Player B wins");
    }
  }
}
 
// this code is contributed shivhack999

                    

Javascript

// Gets the max value
function getMex(s) {
  let mex = 0;
  while (s.has(mex)) {
    mex++;
  }
  return mex;
}
 
// Find check if the rectangle is a part of the
// the original rectangle
function checkOne(mat, i, j, k, l) {
  // initially create the bitset
  // of original intValue
  let m = mat.toString(2).padStart(16, "0");
  m = m.split("").map((i) => parseInt(i));
 
  // Check if it is a part of the rectangle
  for (let x = i; x <= j; x++) {
    for (let y = k; y <= l; y++) {
      const pos = 15 - x * 4 - y;
 
      // If not set, then not part
      if (!m[pos]) {
        return -1;
      }
      m[pos] = 0;
    }
  }
 
  // If part of rectangle
  // then convert to int again and return
  const res = parseInt(m.join(""), 2);
  return res;
}
 
// Recursive function to get the grundy value
function getGrundy(pos, grundy) {
  // If state has been visited
  if (grundy.has(pos)) {
    return grundy.get(pos);
  }
 
  // For obtaining the MEX value
  const gSet = new Set();
 
  // Generate all the possible rectangles
  for (let i = 0; i < 4; i++) {
    for (let j = i; j < 4; j++) {
      for (let k = 0; k < 4; k++) {
        for (let l = k; l < 4; l++) {
          // check if it is part of the original
          // rectangle, if yes then get the int value
          const res = checkOne(pos, i, j, k, l);
 
          // If it is a part of original matrix
          if (res !== -1) {
            // Store the grundy value
            // Memorize
            grundy.set(res, getGrundy(res, grundy));
 
            // Find MEX
            gSet.add(grundy.get(res));
          }
        }
      }
    }
  }
 
  // Return the MEX
  return getMex(gSet);
}
 
// Convert the matrix to INT
function toInt(matrix) {
  let h = 0;
 
  // Traverse in the matrix
  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      h = 2 * h + matrix[i][j];
    }
  }
  return h;
}
 
// Driver Code
const mat = [  [0, 1, 1, 0],
  [0, 0, 0, 0],
  [0, 0, 0, 0],
  [0, 0, 0, 1]
];
 
// Get the int value of the matrix
const intValue = toInt(mat);
 
const grundy = new Map();
 
// Base case
grundy.set(0, 0);
 
// If returned value is non-zero
if (getGrundy(intValue, grundy)) {
console.log("Player A wins");
} else {
console.log("Player B wins");
}

                    

Output
Player A wins

Time Complexity: O(N2), we are using recursion which will cost us O(81*N) and we are also using nested loops to traverse the matrix which will cost us O(N*N) time.
Auxiliary Space: O(N2), we are using extra space for the array grundy which will be of size N*N in the worst case.



Last Updated : 16 Oct, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads