The Castle

Algorithm

First, we find the rooms with recursive flood-fill. This gives us the largest room size and the number of rooms. Then, we check all possible pairs of rooms to see if they share a border wall. If they do, we add the border-wall to optimalWalls and take note of the size of the room created when the wall is removed. If a created room is larger than the previous record, we delete all walls previously stored in optimalWalls. We also check for some time-consuming edge cases and solve them without running the optimal-wall function.

Python

def getWalls(num):
    # Returns a four-item list where each item represents the
    # south, east, north and west walls respectively. Each item
    # is 1 if a wall exists, and 0 otherwise.
    return list(map(int,list(bin(num)[2:].zfill(4))))

def findRoom(start,matrix,visited):
    # Calculates all modules in the same room as the start module.
    # start: the point to start the flood-fill.
    # matrix: 2D array containing the number of each module.
    # visited: list of vertices that have already been visited.
    if start in visited:
        return []
    res = set()
    res.add(start)
    walls = getWalls(matrix[start[0]][start[1]])
    # Warning: dumb code ahead
    if(not(walls[0])): res.update(findRoom((start[0]+1,start[1]),matrix,visited|res)) # south
    if(not(walls[1])): res.update(findRoom((start[0],start[1]+1),matrix,visited|res)) # east
    if(not(walls[2])): res.update(findRoom((start[0]-1,start[1]),matrix,visited|res)) # north
    if(not(walls[3])): res.update(findRoom((start[0],start[1]-1),matrix,visited|res)) # west
    return res

def findAllRooms(matrix):
    visited = set()
    rooms = []
    for y in range(len(matrix)):
        for x in range(len(matrix[y])):
            if (y,x) not in visited:
                c = findRoom((y,x),matrix,visited)
                rooms.append(c)
                visited.update(c)
    return rooms

def areRoomsBordering(room1,room2):
    borderWalls = []
    for i in room1:
        for x in [[-1,0],[0,1],[1,0],[0,-1]]:
            n = (i[0]+x[0],i[1]+x[1])
            if n in room2:
                k = [i[0]+1,i[1]+1]
                if x == [-1,0]: direction = 'N'
                elif x == [0,1]: direction = 'E'
                elif x == [1,0]:
                    direction = 'N'
                    k = [i[0]+2,i[1]+1]
                elif x == [0,-1]:
                    direction = 'E'
                    k = [i[0]+1,i[1]]
                borderWalls.append([k,direction]) # They start counting modules from 1
    if len(borderWalls) == 0: return False
    return borderWalls

def findLargestRoom(rooms):
    # Find the largest room creatable by removing one wall.
    optimalWalls = []
    largestRoomSize = 0
    for r1 in range(len(rooms)):
        room1 = rooms[r1]
        for r2 in range(r1):
            room2 = rooms[r2]
            roomSize = len(room1) + len(room2)
            if roomSize >= largestRoomSize:
                borderWalls = areRoomsBordering(room1,room2)
                if borderWalls:
                    if len(room1) + len(room2) > largestRoomSize:
                        largestRoomSize = roomSize
                        optimalWalls = []
                    optimalWalls += borderWalls
    #print(optimalWalls)
    return largestRoomSize, sorted(sorted(sorted(optimalWalls,key=lambda i:['N','E'].index(i[1])),key=lambda i:-i[0][0]),key=lambda i:i[0][1])[0]

def parseInput(filename):
    with open(filename,'r') as fIn:
        lines = fIn.readlines()
        m,n = map(int,lines[0].split())
        matrix = [[None for _ in range(m)] for _ in range(n)]
        for y in range(n):
            for x in range(m):
                matrix[y][x] = int(lines[y+1].split()[x])
    return matrix

def main():
    matrix = parseInput('castle.in')
    print('input parsed')
    rooms = findAllRooms(matrix)
    print('rooms found')
    numRooms = len(rooms)
    maxRoom = len(max(rooms, key=len))
    print('largest room found')
    if max(list(map(len,rooms))) == 1:
        # If all the rooms are only one module in size, there's no need to run through all the rooms
        if len(matrix) > 1: d = 'N'
        else: d = 'E'
        wallRemoval = [2, [[len(matrix),1],d]]
    else: wallRemoval = findLargestRoom(rooms)
    print('wall found')
    with open('castle.out','w') as fOut:
        fOut.write(str(numRooms)+'\n')
        fOut.write(str(maxRoom)+'\n')
        fOut.write(str(wallRemoval[0])+'\n')
        fOut.write('%d %d %s\n' %(wallRemoval[1][0][0], wallRemoval[1][0][1], wallRemoval[1][1]))

if __name__ == '__main__':
    main()