Python을 이용한 14500번 : 테트로미노에 대해서 포스팅하겠습니다. 14500번은 브루트포스 알고리즘, 구현 알고리즘으로 분류되는 문제입니다.
1. 14500번 문제설명
- 입력 데이터: 첫 줄에 종이의 세로 크기
N
과 가로 크기M
이 주어집니다. 이후N
개의 줄에 걸쳐 각 칸에 쓰여 있는 수가 주어집니다. 이 수들은 1,000을 넘지 않는 자연수입니다. - 목표: 테트로미노를 적절히 배치하여 얻을 수 있는 숫자들의 합의 최대값을 찾습니다.
2. 14500번 접근방법
- 기본 설정: 입력을 받고,
visited
배열을 이용해 탐색한 위치를 표시합니다.max_count
변수를 이용해 현재까지의 최대 합을 저장합니다. - DFS 함수 구현: 현재 위치에서 시작해 4칸을 탐색하는 DFS 함수를 구현합니다. 이 함수는 현재까지 탐색한 칸의 수(
count
)와 현재까지의 합(score
)을 인자로 받습니다. 각 단계에서는 4가지 방향(상, 하, 좌, 우)으로 탐색을 시도합니다. - 특수 모양 처리: ‘ㅗ’ 모양 같은 테트로미노는 일반적인 DFS 탐색으로 찾을 수 없으므로, 3번째 칸에서 다른 방향으로 탐색을 시도하는 특별한 로직을 추가합니다.
- count가 2일 때 특수 모양 탐색
- 아래 코드에서
count
변수는 현재까지 탐색한 테트로미노의 칸 수를 나타냅니다. DFS 탐색을 시작할 때count
는 1로 시작하며, 테트로미노의 각 칸을 탐색할 때마다 1씩 증가합니다. count == 2
는 현재까지 두 칸을 탐색했음을 의미합니다. 즉, 시작점에서 한 번 탐색을 진행해 두 번째 칸에 도달한 상태입니다.- 이 시점에서 ‘ㅗ’ 모양과 같은 특수 모양을 탐색하기 위해, 다음 칸을 탐색하는 대신 현재 칸에서 다른 방향으로 추가 탐색을 시도합니다. 이를 통해 ‘ㅗ’, ‘ㅜ’, ‘ㅏ’, ‘ㅓ’와 같은 모양을 형성할 수 있는지 확인합니다.
- 아래 코드에서
- ‘ㅗ’ 모양 테트로미노 처리 방법
- ‘ㅗ’ 모양은 세 번째 칸에서 한 칸을 더 탐색한 후, 네 번째 칸을 탐색하지 않고 다시 두 번째 칸으로 돌아와 다른 방향의 세 번째 칸을 탐색하는 특수한 형태를 가지고 있습니다.
count == 2
일 때, 즉 두 번째 칸을 탐색한 후에, 현재 위치(nx
,ny
)를 방문 표시하고, 원래 위치(x
,y
)를 기준으로 추가 탐색을 진행합니다. 이때,score + temp
로 현재까지의 점수에 방문한 칸의 점수를 더해 전달합니다.- 이 추가 탐색은 기존의 경로를 벗어난 새로운 방향으로 탐색을 시도하는 것으로, 특수 모양을 찾기 위한 탐색입니다.
- 탐색이 끝난 후에는
visited[nx][ny] = False
로 설정하여, 다른 탐색 경로에서 이 칸을 사용할 수 있도록 합니다.
- count가 2일 때 특수 모양 탐색
- 경계 조건 및 방문 처리: 탐색 중에는
visited
배열을 업데이트하여 이미 탐색한 위치를 다시 탐색하지 않도록 합니다. 탐색이 끝나면 방문 표시를 원래대로 되돌립니다. - 최대값 갱신: 각 탐색에서 얻은 합이 현재까지의 최대값보다 크다면
max_count
를 갱신합니다. - 전체 탐색 실행: 종이의 모든 칸을 시작점으로 하여 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로 대폭 개선되었습니다.