백준 14500 – 테트로미노 

Python을 이용한 14500번 : 테트로미노에 대해서 포스팅하겠습니다. 14500번은 브루트포스 알고리즘, 구현 알고리즘으로 분류되는 문제입니다.

boj-14500 title

1. 14500번 문제설명

  • 입력 데이터: 첫 줄에 종이의 세로 크기 N과 가로 크기 M이 주어집니다. 이후 N개의 줄에 걸쳐 각 칸에 쓰여 있는 수가 주어집니다. 이 수들은 1,000을 넘지 않는 자연수입니다.
  • 목표: 테트로미노를 적절히 배치하여 얻을 수 있는 숫자들의 합의 최대값을 찾습니다.

2. 14500번 접근방법

  1. 기본 설정: 입력을 받고, visited 배열을 이용해 탐색한 위치를 표시합니다. max_count 변수를 이용해 현재까지의 최대 합을 저장합니다.
  2. DFS 함수 구현: 현재 위치에서 시작해 4칸을 탐색하는 DFS 함수를 구현합니다. 이 함수는 현재까지 탐색한 칸의 수(count)와 현재까지의 합(score)을 인자로 받습니다. 각 단계에서는 4가지 방향(상, 하, 좌, 우)으로 탐색을 시도합니다.
  3. 특수 모양 처리: ‘ㅗ’ 모양 같은 테트로미노는 일반적인 DFS 탐색으로 찾을 수 없으므로, 3번째 칸에서 다른 방향으로 탐색을 시도하는 특별한 로직을 추가합니다.
    • count가 2일 때 특수 모양 탐색
      • 아래 코드에서 count 변수는 현재까지 탐색한 테트로미노의 칸 수를 나타냅니다. DFS 탐색을 시작할 때 count는 1로 시작하며, 테트로미노의 각 칸을 탐색할 때마다 1씩 증가합니다.
      • count == 2는 현재까지 두 칸을 탐색했음을 의미합니다. 즉, 시작점에서 한 번 탐색을 진행해 두 번째 칸에 도달한 상태입니다.
      • 이 시점에서 ‘ㅗ’ 모양과 같은 특수 모양을 탐색하기 위해, 다음 칸을 탐색하는 대신 현재 칸에서 다른 방향으로 추가 탐색을 시도합니다. 이를 통해 ‘ㅗ’, ‘ㅜ’, ‘ㅏ’, ‘ㅓ’와 같은 모양을 형성할 수 있는지 확인합니다.
    • ‘ㅗ’ 모양 테트로미노 처리 방법
      • ‘ㅗ’ 모양은 세 번째 칸에서 한 칸을 더 탐색한 후, 네 번째 칸을 탐색하지 않고 다시 두 번째 칸으로 돌아와 다른 방향의 세 번째 칸을 탐색하는 특수한 형태를 가지고 있습니다.
      • count == 2일 때, 즉 두 번째 칸을 탐색한 후에, 현재 위치(nx, ny)를 방문 표시하고, 원래 위치(x, y)를 기준으로 추가 탐색을 진행합니다. 이때, score + temp로 현재까지의 점수에 방문한 칸의 점수를 더해 전달합니다.
      • 이 추가 탐색은 기존의 경로를 벗어난 새로운 방향으로 탐색을 시도하는 것으로, 특수 모양을 찾기 위한 탐색입니다.
      • 탐색이 끝난 후에는 visited[nx][ny] = False로 설정하여, 다른 탐색 경로에서 이 칸을 사용할 수 있도록 합니다.
  4. 경계 조건 및 방문 처리: 탐색 중에는 visited 배열을 업데이트하여 이미 탐색한 위치를 다시 탐색하지 않도록 합니다. 탐색이 끝나면 방문 표시를 원래대로 되돌립니다.
  5. 최대값 갱신: 각 탐색에서 얻은 합이 현재까지의 최대값보다 크다면 max_count를 갱신합니다.
  6. 전체 탐색 실행: 종이의 모든 칸을 시작점으로 하여 DFS 탐색을 실행합니다. 이 과정에서 모든 가능한 테트로미노의 위치와 모양에 대해 합을 계산하고 최대값을 갱신합니다.

3. 14500번 코드

Python
# https://www.acmicpc.net/problem/14500
from sys import stdin

# 입력: 종이의 세로 크기 N과 가로 크기 M
n, m = map(int, stdin.readline().split())
# graph: 각 칸에 쓰여 있는 수를 저장하는 2차원 리스트
graph = [list(map(int, stdin.readline().split())) for _ in range(n)]
# visited: 각 칸의 방문 여부를 확인하는 2차원 리스트
visited = [[False] * m for _ in range(n)]
# highest_value: 보드에서 가장 큰 값, 나머지 탐색을 최적화하기 위해 사용
highest_value = max(map(max, graph))
# max_count: 테트로미노가 놓인 칸에 쓰인 수들의 합의 최대값
max_count = 0

def dfs(x, y, count, score):
    global max_count, graph
    # 탐색을 중단하는 조건: 남은 칸으로 얻을 수 있는 최대 점수를 더해도 현재 max_count보다 작은 경우
    if max_count > score + highest_value * (4 - count):
        return
    # 기저 조건: 4칸 모두 탐색했을 때, max_count 갱신
    if count == 4:
        max_count = max(max_count, score)
        return
    # 탐색 방향: 상, 하, 좌, 우
    for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
        nx, ny = x + dx, y + dy
        # 경계 조건 확인 및 방문하지 않은 칸에 대해서만 탐색
        if 0 <= nx < n and 0 <= ny < m and not visited[nx][ny]:
            # 임시로 점수 저장, 이를 통해 탐색 후 원래 상태로 복구
            temp = graph[nx][ny]
            # 'ㅗ' 모양 테트로미노 처리를 위한 조건, 3번째 칸에서 추가 탐색
            if count == 2:
                # 'ㅗ' 모양과 같이 특정 지점에서 방향 전환을 통해 탐색해야 하는 경우,
                # 현재 방문한 지점(nx, ny)을 방문 처리하고,
                # 원래 위치(x, y)를 기준으로 다음 칸을 탐색하지 않고,
                # 즉시 다른 방향으로의 추가 탐색을 시도합니다.
                # 이는 'ㅗ', 'ㅜ', 'ㅏ', 'ㅓ' 모양을 포함하여 테트로미노가
                # 일렬로만 이어지지 않는 특수한 형태를 처리하기 위한 것입니다.
                visited[nx][ny] = True
                # 추가 탐색을 통해 현재 위치에서 다른 방향으로의 점수를 계산하며,
                # 이때 count를 증가시켜 탐색 깊이를 조절합니다.
                # score + temp는 현재까지의 점수에 방문한 칸의 점수를 추가한 것입니다.
                dfs(x, y, count + 1, score + temp)
                # 탐색이 끝난 후, 방문 처리를 해제하여 다른 경로 탐색 시 재사용할 수 있도록 합니다.
                visited[nx][ny] = False


# 모든 칸에 대해 DFS 수행
for i in range(n):
    for j in range(m):
        # 현재 칸을 시작점으로 설정
        visited[i][j] = True
        dfs(i, j, 1, graph[i][j])
        # 탐색이 끝난 후 방문 상태 초기화
        visited[i][j] = False

# 최대 점수 출력
print(max_count)

4. 14500번 시간초과 해결

처음에 백준 14500번 문제를 풀었을 때, 실행 시간이 4388ms로 나와서 굉장히 놀랐습니다. 정답 코드를 참고한 후 if max_count > score + highest_value * (4 - count): 이 조건을 추가하니 실행 시간이 184ms로 대폭 개선되었습니다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Scroll to Top