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
#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;
} |
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" );
}
}
} |
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" )
|
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 |
// 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" );
} |
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.