ramge132 / SSAFY_Daejeon_Algorithm

SSAFY12기 대전 알고리즘 스터디
4 stars 7 forks source link

[삼성 SW 2024 상반기 오전 1번] 고대 문명 유적 탐사 #55

Open ramge132 opened 2 months ago

ramge132 commented 2 months ago

https://www.codetree.ai/training-field/frequent-problems/problems/ancient-ruin-exploration/description?page=1&pageSize=20

happypildo commented 1 month ago
from collections import deque
import heapq

RELIC_SIZE = 5
DIRECTION = [[-1, 0], [1, 0], [0, -1], [0, 1]]

class RelicValue:
    # 첫 번째 단계에서, 최고 우선 순위의 유물을 찾기 위한 클래스
    def __init__(self, value, degree, x, y, points, rot_idx):
        """
        Args:
            value: 유물 개수
            degree: 돌린 각도
            x: 중점 행 좌표
            y: 중점 열 좌표
            points: 유물들 좌표
            rot_idx: 미리 만들어 놓은 회전 배열의 좌표
        """
        self.value = value
        self.degree = degree
        self.x = x
        self.y = y
        self.points = points
        self.rot_idx = rot_idx

    def __lt__(self, other):
        # 가치가 높을수록 우선 순위
        if self.value > other.value:
            return True
        elif self.value < other.value:
            return False

        # 가치가 같다면, 각도가 작을수록 우선 순위
        if self.degree < other.degree:
            return True
        elif self.degree > other.degree:
            return False

        # 각도가 같다면, 열이 작을수록 우선 순위
        if self.y < other.y:
            return True
        elif self.y > other.y:
            return False

        # 열이 같아면, 행이 작을수록 우선 순위
        if self.x < other.x:
            return True
        elif self.x > other.x:
            return False

class Location:
    # 유물을 발굴하고, 남은 공간에 집어 넣기 위한 우선순위 계산
    def __init__(self, x, y):
        """
        Args:
            x: 빈 곳의 행
            y: 빈 곳의 열
        """
        self.x = x
        self.y = y

    def __lt__(self, other):
        # 열이 작을수록 우선 순위
        if self.y < other.y:
            return True
        elif self.y > other.y:
            return False

        # 열이 같다면, 행이 클수록 우선 순위
        if self.x > other.x:
            return True
        else:
            return False

    def __str__(self):
        return f"{self.x} - {self.y}"

def bfs_without_replace(loc, relic_map):
    """
    BFS를 수행할 때, 기존 유물 지도를 돌리는 것이 아닌, 이미 돌아간 좌표 정보를 활용하여 BFS 수행
    Args:
        loc: 이미 돌아간 좌표 정보 (5x5)
        relic_map: 기존 맵

    Returns:
        탐색을 통해 얻어낸 유물 개수
        유물들의 좌표
    """
    num_of_relics = 0

    is_visited = set()
    queue = deque()
    searched_points = []

    for i in range(RELIC_SIZE):
        for j in range(RELIC_SIZE):
            if (i, j) in is_visited:
                continue

            rot_x, rot_y = loc[i][j]

            is_visited.add((i, j))
            queue.append((i, j))
            temp_searched_points = [(i, j)]

            # 좌표만 돌아간 것을 참고한다.
            target_color = relic_map[rot_x][rot_y]
            cnt = 1

            while queue:
                x, y = queue.popleft()

                for dx, dy in DIRECTION:
                    temp_x, temp_y = x + dx, y + dy

                    if (-1 < temp_x < RELIC_SIZE) and (-1 < temp_y < RELIC_SIZE):
                        if (temp_x, temp_y) in is_visited:
                            continue

                        rot_x, rot_y = loc[temp_x][temp_y]

                        # 좌표만 돌아간 것을 참고한다.
                        if target_color == relic_map[rot_x][rot_y]:
                            is_visited.add((temp_x, temp_y))
                            queue.append((temp_x, temp_y))
                            temp_searched_points.append((temp_x, temp_y))
                            cnt += 1

            if cnt > 2:
                num_of_relics += cnt
                searched_points.extend(temp_searched_points)

    return num_of_relics, searched_points

def search_relic(rotated_locations, rot_info, relic_map):
    """
    모든 회전 경우의 수를 돌아보면서, 가장 큰 유물 조각을 가질 수 있을 때를 확인한다.
    Args:
        rotated_locations: 미리 회전 시킨 배열 정보
        rot_info: RelicValue 인스턴스를 만들기 위한 정보를 사전에 정의한 배열
        relic_map: 유물 정보

    Returns:
        우선 순위에 따라 구해진 가장 많은 유물 수, 바뀐 유물 정보, 발견한 유물 위치 정보
    """
    # 우선순위를 위해 heapq 사용
    min_heap = []
    for idx, loc in enumerate(rotated_locations):
        # 매 회전마다 BFS를 수행하여 얻어 낸 유물 개수 등의 정보
        num_of_relics, searched_points = bfs_without_replace(loc, relic_map)
        # 정보를 heap에 담기
        heapq.heappush(min_heap, RelicValue(num_of_relics, *rot_info[idx], searched_points, idx))

    # 최종적으로 맨 위에 그 정보가 담기게 됨
    result = heapq.heappop(min_heap)
    largest_search = result.points
    largest_nums = result.value
    largest_idx = result.rot_idx

    # 해당 회전 경우의 수로 일부분 회전
    temp_relic_map = [relic_map[i][:] for i in range(RELIC_SIZE)]
    for i in range(RELIC_SIZE):
        for j in range(RELIC_SIZE):
            rot_x, rot_y = rotated_locations[largest_idx][i][j]
            relic_map[i][j] = temp_relic_map[rot_x][rot_y]

    return largest_nums, relic_map, largest_search

def repeatedly_search(numbers_on_wall, cnt, relic_map, search_result):
    """
    문제에서 2-3단계 반복을 하는 부분으로,
        1. 이전에 찾은 유물을 벽에 적힌 번호로 대체하고
        2. 다시 BFS를 돌려 유물을 찾는다. (이 때 회전은 없다.)
    Args:
        numbers_on_wall: 벽에 적힌 번호 리스트
        cnt: 벽 번호 순서
        relic_map: 유물 정보
        search_result: 이전에 얻어 낸 유물 좌표들

    Returns:
        더 이상 유물을 찾을 수 없는 경우에 반환
            연쇄 과정을 통해 구해진 유물의 개수 총 합
            유물 정보
            벽 순서
    """
    ret = 0
    loc = [[(x, y) for y in range(RELIC_SIZE)] for x in range(RELIC_SIZE)]
    while True:
        # 우선, 유물 발굴로 인해 남은 공간을 채운다. -> 우선 순위를 따져 진행한다.
        min_heap = []
        for x, y in search_result:
            heapq.heappush(min_heap, Location(x, y))

        # 채우는 과정...
        while min_heap:
            item = heapq.heappop(min_heap)
            x, y = item.x, item.y
            relic_map[x][y] = numbers_on_wall[cnt]
            cnt += 1  # 벽에 적힌 순서를 따라가도록 하나 씩 증가

        # 회전이 없음
        num_of_relics, search_result = bfs_without_replace(loc, relic_map)

        # 유물을 발견할 수 없다! break
        if num_of_relics == 0:
            break

        ret += num_of_relics

    return ret, relic_map, cnt

# 미리 배열을 회전한 정보를 저장해 놓자.
original_loc = [[(x, y) for y in range(RELIC_SIZE)] for x in range(RELIC_SIZE)]
rotated_loc = []
rotation_info = []
for y in range(RELIC_SIZE - 2, 0, -1):
    for x in range(RELIC_SIZE - 2, 0, -1):
        new_loc = [original_loc[i][:] for i in range(RELIC_SIZE)]
        # 일부분을 가져와 돌린다.
        partial = [row[y - 1:y + 2] for row in new_loc[x - 1:x + 2]]

        for rot in range(1, 5):
            # 4번 돌리면 원위치가 되기 때문에, 4번 회전한다.
            partial = list(map(list, zip(*partial)))[::-1]

            for _x in range(x - 1, x + 2):
                for _y in range(y - 1, y + 2):
                    new_loc[_x][_y] = partial[_x - (x - 1)][_y - (y - 1)]

            if rot < 4:
                rotated_loc.append([new_loc[i][:] for i in range(RELIC_SIZE)])
                # 우선 순위를 표현하기 위해 각도는 -1을 곱한다.
                rotation_info.append((rot * -1, x, y))

K, M = list(map(int, input().split()))
input_relic = [list(map(int, input().split())) for _ in range(RELIC_SIZE)]
input_numbers = list(map(int, input().split()))

wall_count = 0
answer_list = []
for k_iter in range(K):
    answer = 0

    # 1. 탐사 진행) 중심점을 잡고 회전시키면서 유물을 탐색한다.
    ret1, input_relic, searched_loc = search_relic(rotated_loc, rotation_info, input_relic)
    answer += ret1

    # 2. 반복하면서, 유물을 찾자!
    ret2, input_relic, wall_count = repeatedly_search(input_numbers, wall_count, input_relic, searched_loc)
    answer += ret2

    if answer == 0:
        break
    answer_list.append(answer)

for item in answer_list:
    print(item, end=" ")
print()
seokbangguri commented 1 month ago
from collections import deque
# 상 우 하 좌
dxy = [[0, -1], [1, 0], [0, 1], [-1, 0]]

# 회전 시킨 후 획득 가능한 유물 개수
def canGet(matrix_for_find, explore_list, position, rotating_degree, after_rotating=True):
    # 최종 가져올 유물들
    visited = [[False]*5 for _ in range(5)]
    count = 0

    # bfs 돌릴 좌표들 하나씩 큐에 담고 bfs
    for x, y in explore_list:
        # 한 bfs당 유물들 임시 방문처리
        temp_visited = [[False]*5 for _ in range(5)]
        temp_visited[y][x] = True
        queue = deque([(x, y)])
        temp_cnt = 1
        while queue:
            cx, cy = queue.popleft()

            for dx, dy in dxy:
                nx, ny = cx + dx, cy + dy

                # 다음 좌표가 범위를 넘어간 경우
                if not (0 <= nx <= 4 and 0 <= ny <= 4):
                    continue

                # 이미 방문한 경우
                # 자신의 값과 다른 경우
                if visited[ny][nx] or temp_visited[ny][nx] or matrix_for_find[ny][nx] != matrix_for_find[cy][cx]:
                    continue

                # 방문하지 않고 자신과 값이 같다면 추가
                else:
                    queue.append((nx, ny))
                    temp_visited[ny][nx] = True
                    temp_cnt += 1

        # 연결된 유물이 3개 이상인 경우
        if temp_cnt >= 3:
            count += temp_cnt
            visited = [[visited[j][i] or temp_visited[j][i] for i in range(5)] for j in range(5)]

    return count, position, rotating_degree, visited

# 중심 좌표를 지정 해당 좌표를 중심으로 3*3 회전
def rotate(position, rotating_degree):
    temp_matrix = [a[:] for a in matrix]
    explore_list = set()

    # 돌려야 하는 3*3 배열 추출
    def getTempArray(position):
        temp = []
        cx, cy = position
        for i in range(-1,2):
            temp.append(temp_matrix[cy+i][cx-1:cx+2])
            for j in range(-1, 2):
                explore_list.add((cx + j, cy + i))
        return temp

    temp_arr = getTempArray(position)

    # 90도 회전
    if rotating_degree == 90:
        # 2차원 배열을 뒤집어 전치하면 90도 회전
        temp_arr = list(zip(*temp_arr[::-1]))

    # 180도 회전
    elif rotating_degree == 180:
        # 2차원 배열을 뒤집어 전치하면 90도 회전
        # 해당 회전 2회
        for _ in range(2):
            temp_arr = list(zip(*temp_arr[::-1]))

    # 270도 회전
    elif rotating_degree == 270:
        # 2차원 배열을 뒤집어 전치하면 90도 회전
        # 해당 회전 3회
        for _ in range(3):
            temp_arr = list(zip(*temp_arr[::-1]))

    # 돌린 3*3 배열을 삽입
    for i in range(-1, 2):
        for j in range(-1, 2):
            temp_matrix[position[1]+i][position[0]+j] = temp_arr[i+1][j+1]

    # 삽입완료한 5*5배열, bfs 돌릴 시작 좌표 리스트, 중심좌표, 회전 각도를 인자로 bfs함수 실행
    rotate_result = canGet(temp_matrix, explore_list, position, rotating_degree)

    return rotate_result, temp_matrix

K, M = map(int, input().split())

matrix = [list(map(int, input().split())) for _ in range(5)]

relics = deque(list(map(int, input().split())))

total_relics = 0

result = []

# 1. 탐사 진행 result의 요소 개수가 탐사 횟수
while len(result) < K:
    # 최종 좌표 및 회전 각
    max_relics_count = -1
    min_rotating_degree = float('inf')
    max_rotating_position = (-1, -1)
    max_relics_visited = []
    max_relics_matrix = []

    # 우선순위 고려해서 최종 회전시킬 좌표 및 회전 각 찾기
    for y in range(1, 4):
        for x in range(1, 4):
            for degree in range(90, 271, 90):
                (temp_relics, rotating_position, rotating_degree, temp_visited), temp_matrix = rotate((x, y), degree)

                # 획득 유물 가장 많은 경우
                if temp_relics > max_relics_count:
                    max_relics_count = temp_relics
                    min_rotating_degree = rotating_degree
                    max_rotating_position = rotating_position
                    max_relics_visited = temp_visited
                    max_relics_matrix = temp_matrix

                # 회전한 각도가 가장 작은 경우
                elif temp_relics == max_relics_count and rotating_degree < min_rotating_degree:
                    max_relics_count = temp_relics
                    min_rotating_degree = rotating_degree
                    max_rotating_position = rotating_position
                    max_relics_visited = temp_visited
                    max_relics_matrix = temp_matrix

                # 열이 작거나 같은 경우
                elif temp_relics == max_relics_count and rotating_degree == min_rotating_degree and rotating_position[0] <= max_rotating_position[0]:

                    # 열이 작은 경우
                    if rotating_position[0] < max_rotating_position[0]:
                        max_relics_count = temp_relics
                        min_rotating_degree = rotating_degree
                        max_rotating_position = rotating_position
                        max_relics_visited = temp_visited
                        max_relics_matrix = temp_matrix

                    # 행이 작은 경우
                    elif rotating_position[1] < max_rotating_position[1]:
                        max_relics_count = temp_relics
                        min_rotating_degree = rotating_degree
                        max_rotating_position = rotating_position
                        max_relics_visited = temp_visited
                        max_relics_matrix = temp_matrix

    # 탐사 후 발견된 유물이 0개인 경우 탐사 종료
    if max_relics_count == 0:
        break

    total_relics += max_relics_count

    # 유물 채우기
    for i in range(5):
        for j in range(4, -1, -1):
            if max_relics_visited[j][i]:
                max_relics_matrix[j][i] = relics.popleft()

    matrix = [a[:] for a in max_relics_matrix]

    while True:
        # 연쇄 유물 획득
        temp_relics, rotating_position, rotating_degree, temp_visited = canGet(matrix, {(i, j) for i in range(5) for j in range(5)}, (-1, -1), 0, after_rotating=False)
        # 유물을 획득한 경우
        if temp_relics:
            total_relics += temp_relics
            # 유물 채우기
            for i in range(5):
                for j in range(4, -1, -1):
                    if temp_visited[j][i]:
                        matrix[j][i] = relics.popleft()

        # 유물을 획득하지 못한 경우
        else:
            result.append(str(total_relics))
            total_relics = 0
            break

print(" ".join(result))
ramge132 commented 1 month ago
import sys
sys.stdin = open('input.txt')

from collections import deque

def rotate_subgrid(grid, i, j, angle):
    # 3×3 부분 그리드 추출
    subgrid = [row[j:j + 3] for row in grid[i:i + 3]]

    '''
    회전 규칙:
    90도 회전: (row, col) → (n - 1 - col, row)
    180도 회전: (row, col) → (n - 1 - row, n - 1 - col)
    270도 회전: (row, col) → (col, n - 1 - row)
    '''
    if angle == 90:
        rotated = [[subgrid[2 - col][row] for col in range(3)] for row in range(3)]
    elif angle == 180:
        rotated = [[subgrid[2 - row][2 - col] for col in range(3)] for row in range(3)]
    elif angle == 270:
        rotated = [[subgrid[col][2 - row] for col in range(3)] for row in range(3)]
    else:
        rotated = subgrid  # 무회전슛

    # 회전된 부분 그리드를 원래 그리드에 반영
    for row in range(3):
        for col in range(3):
            grid[i + row][j + col] = rotated[row][col]

def find_connected_pieces(grid):
    # 상하좌우로 연결된 같은 숫자의 조각을 찾음
    visited = [[False] * 5 for _ in range(5)]
    pieces = []
    for i in range(5):
        for j in range(5):
            if not visited[i][j]:
                num = grid[i][j]  # 현재 좌표값, 연결된 유물 그룹을 찾는 기준
                queue = deque()  # BFS를 위한 큐 생성
                queue.append((i, j))  # 현재 좌표를 큐에 추가
                visited[i][j] = True  # 현재 좌표를 방문 처리
                piece = [(i, j)]  # 현재 좌표를 첫 번째 유물 조각으로 추가

                # BFS 드가자-
                while queue:
                    x, y = queue.popleft()
                    # 상 하 좌 우 이동
                    for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
                        nx, ny = x + dx, y + dy
                        # 그리드 범위 벗어나는지 확인 & 방문 했는지 확인
                        if 0 <= nx < 5 and 0 <= ny < 5 and not visited[nx][ny]:
                            # 유물 조각 번호가 같으면
                            if grid[nx][ny] == num:
                                visited[nx][ny] = True
                                queue.append((nx, ny))  # 큐에 추가
                                piece.append((nx, ny))  # 조각 리스트에 추가 (3개 확인용)

                # 조각 리스트의 길이가 3 이상이면
                if len(piece) >= 3:
                    # 조각 좌표들을 조각들 리스트에 추가
                    pieces.append(piece)
    return pieces

def fill_positions(grid, positions, wall_numbers, wall_index):
    """
    positions: 유물이 사라진 위치 좌표들이 담긴 리스트. 각 좌표는 (row, col) 형식
    """

    # 첫 번째 기준: 열(x[1])이 작은 순서대로 정렬 (오름차순)
    # 두 번째 기준: 행(x[0])은 큰 순서대로 정렬 (내림차순)
    positions.sort(key=lambda x: (x[1], -x[0]))
    for pos in positions:
        # 벽에 적힌 유물 번호(wall_numbers)가 다 소진되면 반복 종료
        if wall_index >= len(wall_numbers):
            break
        grid[pos[0]][pos[1]] = wall_numbers[wall_index]
        wall_index += 1
    return wall_index

def simulate(grid, wall_numbers, wall_index):
    # 유물 획득 시뮬레이션 실행
    total_value = 0
    while True:
        pieces = find_connected_pieces(grid)
        if not pieces:
            break
        positions_to_fill = []  # set 대신 list로 처리
        for piece in pieces:
            total_value += len(piece)
            for x, y in piece:
                grid[x][y] = 0
                positions_to_fill.append((x, y))

        wall_index = fill_positions(grid, positions_to_fill, wall_numbers, wall_index)
    return total_value, wall_index

K, M = map(int, input().split())
grid = [list(map(int, input().split())) for _ in range(5)]
wall_numbers = list(map(int, input().split()))
wall_index = 0
turn_count = 0
result_list = []

while turn_count < K:
    best_total_value = -1
    best_rotation = None
    for i in range(3):
        for j in range(3):
            for angle in [90, 180, 270]:
                # 그리드 복사 (deepcopy 대신 메모리 효율적인 방법 사용)
                temp_grid = [row[:] for row in grid]
                rotate_subgrid(temp_grid, i, j, angle)
                temp_wall_index = wall_index
                temp_total_value, temp_wall_index = simulate(temp_grid, wall_numbers, temp_wall_index)
                if temp_total_value > best_total_value:
                    best_total_value = temp_total_value
                    best_rotation = (i, j, angle)
                elif temp_total_value == best_total_value:
                    # 회전 각도가 작은 것을 우선 선택
                    if angle < best_rotation[2]:
                        best_rotation = (i, j, angle)
                    elif angle == best_rotation[2]:
                        # 열이 작은 것을 우선 선택
                        if j < best_rotation[1]:
                            best_rotation = (i, j, angle)
                        elif j == best_rotation[1]:
                            # 행이 작은 것을 우선 선택
                            if i < best_rotation[0]:
                                best_rotation = (i, j, angle)
    if best_total_value == 0:
        # 유물을 획득할 수 없는 경우 탐사 종료
        break
    else:
        i, j, angle = best_rotation
        rotate_subgrid(grid, i, j, angle)
        total_value, wall_index = simulate(grid, wall_numbers, wall_index)
        result_list.append(total_value)
        turn_count += 1
print(' '.join(map(str, result_list)))

회전 공식

1 결론적으로 위 세가지를 알면 끝이다.

순서는 우선 배제하고 방향과 행,열에 대한 개념을 정의하자.

2 이것은 forward이다.

3 이것은 backward이다.

origin을 90도 돌린 그리드를 만든다고 생각해보자. 그럴 경우 저장할 리스트에,

  1. [7, 4, 1]을 append
  2. [8, 5, 2]을 append
  3. [9, 6, 3]을 append 할 것이다.

여기서, 뒤에 따질 target이란 첫번째로 append하는 리스트이다. 즉, [7, 4, 1]이 target이다.

gif1 origin으로 target을 만드려고 한다.

[7, 4, 1]은 origin을 기준으로 col(열) 그리고 backward(역방향)이다.

우리가 이것을 처음 따지게 됐으므로 순서는 1이다.

gif2 (오탈자: 행, 열 : row) append 하게되는 순서는 아래와 같다.

  1. [7, 4, 1]
  2. [8, 5, 2]
  3. [9, 6, 3]

origin에서 이것은 row(행) 그리고 forward(순방향)이다.

두 번째로 따지게 됐으니 순서는 2이다.

4 우리가 알게된 순서와 행 or 열 그리고 방향을 다음 공식에 대입하면 코드가 완성된다.

def rotate_subgrid(grid, i, j, angle):
    # 3×3 부분 그리드 추출
    subgrid = [row[j:j + 3] for row in grid[i:i + 3]]

    '''
    기본틀: subgrid[순서1][순서2]
    forward = 행 or 열
    backward = (n-1)-(행 or 열)

    회전 규칙:
    90도 회전: (row, col) → (n - 1 - col, row)
    180도 회전: (row, col) → (n - 1 - row, n - 1 - col)
    270도 회전: (row, col) → (col, n - 1 - row)
    '''
    if angle == 90:
        rotated = [[subgrid[2 - col][row] for col in range(3)] for row in range(3)]
    elif angle == 180:
        rotated = [[subgrid[2 - row][2 - col] for col in range(3)] for row in range(3)]
    elif angle == 270:
        rotated = [[subgrid[col][2 - row] for col in range(3)] for row in range(3)]
    else:
        rotated = subgrid

번외) 메모리 사용량 확인

사용하는 메모리를 확인하는 방법은 2가지가 있다.

  1. psutil (외부 라이브러리로서, pip install psutil을 해야함)
  2. tracemalloc (설치 필요 없음)

psutil

import psutil
import os

def get_memory_usage():
    # 현재 실행 중인 파이썬 프로세스의 정보를 가져옴
    process = psutil.Process(os.getpid())

    # 해당 프로세스의 메모리 정보를 가져옴
    mem_info = process.memory_info()

    # 메모리 정보 중 실제 메모리 사용량(RSS, Resident Set Size)을 반환 (단위: 바이트)
    return mem_info.rss  # in bytes

print(f"Current memory usage: {get_memory_usage() / (1024 * 1024)} MB")
"""
바이트값으로 리턴되기 때문에
(1024 * 1024)로 나눠줌
- 1byte * 1024 = KB
- 1byte * 1024 * 1024 = MB
"""

tracemalloc

import tracemalloc

# 메모리 추적 시작
tracemalloc.start()

# 프로그램 코드 실행

# 메모리 사용량 측정
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current / (1024 * 1024)} MB; Peak: {peak / (1024 * 1024)} MB")

# 메모리 추적 종료
tracemalloc.stop()
Yeonri commented 1 month ago
# 5*5 격자
# 3*3 격자를 선택해서 회전 -> 회전이 가능한 위치를 선택하도록 한다.
# 회전 함수를 설정 90도로 설정한 후, 2배 3배를 한다. -> 90 180 270
# 획득 가치를 최대화 -> 회전 각도가 제일 작은 것 선택
# 회전 중심 좌표의 열이 가장 작은 구간, 열이 같다면 행이 가장 작은 구간
# 새로운 유물 생성 -> 열 다음 행  for i in range(N-1, -1, -1) for j in range(0, N - 1)
# 유물 연쇄 획득 가능

# 1. 유물 획득이 가능한지 확인
# 2. 회전 -> 유물 획득 확인
# 3. 유물 가능하면 획득 후, 채워놓기
# 4. 유물 획득 가능 확인

# 가능 범위 (1,1) ~ (4,4)

# K 탐사 반복 횟수, M 유물 조각 개수
from collections import deque
from collections import defaultdict
from copy import deepcopy

DXY = [(1, 0), (-1, 0),(0, 1), (0, -1)]

def rotate(matrix, init_i, init_j):
    new_matrix = [x[:] for x in matrix] # 복사한 메트릭스를 다시 복사

    for i in range(-1, 2):
        for j in range(-1, 2):
            new_matrix[init_i + j][init_j - i] = matrix[init_i + i][init_j + j]

    return new_matrix

def bfs(matrix, visited, i, j, flag):
    s_x, s_y = (i, j)
    queue = deque([(s_x, s_y)])

    tmp_dir = set()
    tmp_dir.add((s_x, s_y))
    visited.add((s_x, s_y))

    count = 1

    while queue:
        x, y = queue.popleft()

        for dx, dy in DXY:
            nx, ny = x + dx, y + dy

            if 0 <= nx < N and 0 <= ny < N and (nx, ny) not in visited and matrix[x][y] == matrix[nx][ny]:
                tmp_dir.add((nx, ny))
                visited.add((nx, ny))
                count += 1
                queue.append((nx, ny))

    if count >= 3:
        if flag:
            for x, y in tmp_dir:
                matrix[x][y] = 0
        return count
    return 0

def chk_matrix(matrix, flag):

    # 각 방문 좌표들을 vistied set에 저장
    # 탐색을 전체 값에 대해 하지 않고, 회전한 값들에 대해서만 탐색을 하도록 만든다.
    # 모든 방향에 대해서 탐색을 진행하도록 한다.

    # 탐색을 줄이기 위한 위의 방식에 대한 엣지 케이스
    # 만약에 회전을 하지 않는 부분에 연결이 되어있는 케이스는 탐색을 하지 못한다.

    # 따라서 모든 좌표를 탐색하도록 설정을 먼저 해본다.

    visited = set()
    result = 0

    for i in range(N):
        for j in range(N):
            if (i, j) not in visited:
                result += bfs(matrix, visited, i, j, flag)

    return result

N = 5 # 매트릭스 크기
S = 3 # 회전 크기

K, M = map(int, input().split())
matrix = [list(map(int, input().split()))for _ in range(N)]
pre_number = deque(list(map(int, input().split())))

result = []

for _ in range(K): # 탐색 횟수
    max_count = 0
    for rotation_count in range(1, 4):
        for init_j in range(1, N-1): # 중심 좌표 선택
            for init_i in range(1, N-1):

                new_matrix = [x[:] for x in matrix] # 깊은 복사

                for _ in range(rotation_count): # 회전 횟수 만큼 회전
                    new_matrix = rotate(new_matrix, init_i, init_j)

                # 최대 값을 가지는 matrix를 먼저 확인할 수 있도록 flag 설정
                # False는 유물 발굴을 하지 않는다.
                tmp_count = chk_matrix(new_matrix, False)

                if max_count < tmp_count:
                    max_count = tmp_count
                    result_matrix = new_matrix

    if max_count == 0:
        break

    count = 0

    # 최종적으로 선택된 matrix를 이용해서 유물 발굴을 시작

    while True:
        t = chk_matrix(result_matrix, True)
        if t == 0:
            break
        count += t

        for j in range(N):
            for i in range(4, -1, -1):
                if result_matrix[i][j] == 0:
                    result_matrix[i][j] = pre_number.popleft()

    result.append(count)

    # 다음 단계를 위해 result_matrix를 원본 matrix에 저장
    matrix = result_matrix

print(*result)