알고리즘

[백준] 14500 테트로미노

2025년 08월 31일
34

[Gold IV] 테트로미노 - 14500

문제 링크

성능 요약

메모리: 38396 KB, 시간: 6340 ms

분류

구현, 브루트포스 알고리즘

문제 설명

폴리오미노란 크기가 1×1인 정사각형을 여러 개 이어서 붙인 도형이며, 다음과 같은 조건을 만족해야 한다.

정사각형은 서로 겹치면 안 된다. 도형은 모두 연결되어 있어야 한다. 정사각형의 변끼리 연결되어 있어야 한다. 즉, 꼭짓점과 꼭짓점만 맞닿아 있으면 안 된다.

정사각형 4개를 이어 붙인 폴리오미노는 테트로미노라고 하며, 다음과 같은 5가지가 있다.

아름이는 크기가 N×M인 종이 위에 테트로미노 하나를 놓으려고 한다. 종이는 1×1 크기의 칸으로 나누어져 있으며, 각각의 칸에는 정수가 하나 쓰여 있다.

테트로미노 하나를 적절히 놓아서 테트로미노가 놓인 칸에 쓰여 있는 수들의 합을 최대로 하는 프로그램을 작성하시오.

테트로미노는 반드시 한 정사각형이 정확히 하나의 칸을 포함하도록 놓아야 하며, 회전이나 대칭을 시켜도 된다.

입력

첫째 줄에 종이의 세로 크기 N과 가로 크기 M이 주어진다. (4 ≤ N, M ≤ 500)

둘째 줄부터 N개의 줄에 종이에 쓰여 있는 수가 주어진다. i번째 줄의 j번째 수는 위에서부터 i번째 칸, 왼쪽에서부터 j번째 칸에 쓰여 있는 수이다. 입력으로 주어지는 수는 1,000을 넘지 않는 자연수이다.

출력

첫째 줄에 테트로미노가 놓인 칸에 쓰인 수들의 합의 최댓값을 출력한다.


문제 풀이

문제 분석

문제는 N x M 크기의 격자판에 쓰여 있는 숫자들 중, 테트로미노(정사각형 4개를 이어 붙인 도형)를 놓았을 때 테트로미노가 덮는 칸에 있는 숫자들의 합의 최댓값을 구하는 것입니다. 테트로미노는 회전하거나 대칭될 수 있습니다.

접근 방법

이 문제는 완전 탐색 (브루트 포스) 방법을 사용하여 해결할 수 있습니다. 가능한 모든 테트로미노 모양을 미리 정의하고, 격자판의 모든 위치에 각 테트로미노 모양을 놓아보면서 합을 계산합니다. 계산된 합 중 최댓값을 찾아 반환합니다. 핵심은 5가지 테트로미노의 모든 가능한 회전/대칭 형태를 효율적으로 생성하고, 격자판 내에서 유효한 위치에 놓는 것입니다. 주어진 코드에서는 DFS를 사용하여 테트로미노의 모든 모양을 생성하고 있습니다.

구현 설명

1. dfs() 함수: 가능한 테트로미노 모양 생성

  • dfs() 함수는 깊이 우선 탐색(DFS)을 사용하여 가능한 모든 테트로미노 모양을 생성합니다. 이 함수는 (0, 0)에서 시작하여 상, 하, 좌, 우 방향으로 이동하며 정사각형을 추가합니다.
  • 큐(deque)를 사용하여 탐색을 관리하고, 현재까지 만들어진 테트로미노 모양(shape)을 큐에 저장합니다.
  • shape의 길이가 4가 되면 (정사각형 4개), 테트로미노 모양이 완성된 것이므로 shapes 집합에 추가합니다. 이때, 모양을 정규화하기 위해 가장 작은 x, y 좌표를 기준으로 모든 좌표를 이동시킨 후 정렬하여 튜플 형태로 저장합니다. 이렇게 하면 회전/대칭된 동일한 모양이 중복으로 저장되는 것을 방지할 수 있습니다.
  • shapesset 자료구조를 사용하여 중복된 모양을 제거합니다.

2. 입력 처리 및 테트로미노 모양 저장

  • 코드에서는 먼저 격자판의 크기 N과 M을 입력받고, 격자판의 숫자들을 2차원 리스트 board에 저장합니다.
  • dfs() 함수를 호출하여 생성된 테트로미노 모양들을 shapes 리스트에 저장합니다.

3. 격자판 전체 탐색 및 최댓값 계산

  • 격자판의 각 칸 (i, j)에 대해, shapes 리스트에 있는 모든 테트로미노 모양을 놓아봅니다.
  • 각 테트로미노 모양을 놓을 때, 격자판의 범위를 벗어나지 않는지 확인합니다. 벗어나지 않는다면, 해당 칸에 있는 숫자들의 합을 계산합니다.
  • 계산된 합이 현재까지 찾은 최댓값(answer)보다 크다면, answer를 갱신합니다.

4. 결과 출력

  • 모든 칸과 모든 테트로미노 모양에 대해 탐색을 완료한 후, answer에 저장된 최댓값을 출력합니다.

⏱복잡도 분석

  • 시간 복잡도:

    • dfs() 함수는 모든 가능한 테트로미노 모양을 생성합니다. 테트로미노는 5가지 종류가 있고, 각 종류마다 최대 8개의 회전/대칭 형태가 있을 수 있습니다 (하지만 dfs로 모든 형태를 생성하기 때문에 더 많은 탐색을 할 수 있습니다). dfs() 함수의 시간 복잡도는 상수 시간에 비례한다고 볼 수 있습니다.
    • 격자판의 모든 칸을 탐색하는 데 O(N * M) 시간이 걸립니다.
    • 각 칸에 대해 모든 테트로미노 모양을 확인하는 데 O(1) 시간이 걸립니다 (테트로미노 모양의 수는 상수).
    • 따라서 전체 시간 복잡도는 O(N * M) 입니다. 하지만, 실제로는 dfs() 함수에서 더 많은 탐색을 수행하고, 파이썬의 느린 실행 속도 때문에 시간 제한에 걸릴 가능성이 있습니다.
  • 공간 복잡도:

    • 격자판을 저장하는 board 리스트는 O(N * M)의 공간을 사용합니다.
    • shapes 리스트는 테트로미노 모양을 저장하며, 최대 O(1)의 공간을 사용합니다 (테트로미노 모양의 수는 상수).
    • dfs() 함수에서 사용하는 큐는 최악의 경우 O(1)의 공간을 사용합니다.
    • 따라서 전체 공간 복잡도는 O(N * M) 입니다.

핵심 포인트

  1. 테트로미노 모양의 효율적인 생성: DFS를 사용하여 중복 없이 모든 가능한 테트로미노 모양을 생성하는 것이 중요합니다. 좌표 정규화를 통해 회전/대칭된 동일한 모양을 중복 저장하는 것을 방지합니다.
  2. 격자판 경계 처리: 테트로미노가 격자판의 경계를 벗어나지 않도록 꼼꼼하게 확인해야 합니다.
  3. 최댓값 갱신: 테트로미노가 놓인 칸에 있는 숫자들의 합을 계산하고, 현재까지 찾은 최댓값보다 크다면 갱신하는 로직을 정확하게 구현해야 합니다.

풀이 코드

import sys
from collections import deque

input = sys.stdin.readline

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

def dfs():
    shapes = set()
    q = deque()
    q.append([(0,0)])
    
    while q:
        shape = q.popleft()
        if len(shape) == 4:
            min_x = min(x for x, _ in shape)
            min_y = min(y for _, y in shape)
            temp = tuple(sorted((x-min_x, y-min_y) for x,y in shape))
            shapes.add(temp)
            continue

        for x, y in shape:
            for dx, dy in directions:
                nx, ny = x + dx, y + dy
                if (nx, ny) not in shape:
                    q.append(shape + [(nx, ny)])
    return list(shapes)


n,m = map(int, input().split())
board = []
for i in range(n):
    board.append(list(map(int, input().split())))

shapes = dfs()

answer = 0
for i in range(n):
    for j in range(m):
        for shape in shapes:
            total = 0
            can_shape = True
            for dx, dy in shape:
                x, y = i + dx, j + dy
                if 0 <= x < n and 0 <= y < m:
                    total += board[x][y]
                else:
                    can_shape = False
                    break
            if can_shape and total > answer:
                answer = total
print(answer)

댓글을 불러오는 중...