
본 문제는 BOJ의 삼성 SW 역량 테스트 기출 문제 문제집에 수록된 문제입니다.

위 그림처럼, 낚시왕은 오른쪽으로 한 칸씩 이동하며 상어를 잡습니다. 이때 1초마다, 아래 적힌 순서대로 일이 일어납니다.
상어는 주어진 속도(칸/초)와 방향으로 이동하고, 격자판의 경계를 넘는 경우에는 방향을 반대로 바꿔서 속력을 유지한채로 이동합니다. 또한 상어가 이동을 마친 후에 한 칸에 상어가 두 마리 이상 있을 수 있습니다. 이때는 크기가 가장 큰 상어가 나머지 상어를 모두 잡아먹습니다.
이때, 낚시왕이 잡은 상어 크기의 합을 구하는 문제입니다.
(2 ≤ R, C ≤ 100, 0 ≤ M ≤ R×C)r, c, s, d, z (1 ≤ r ≤ R, 1 ≤ c ≤ C, 0 ≤ s ≤ 1000, 1 ≤ d ≤ 4, 1 ≤ z ≤ 10000) 로 이루어져 있다. (r, c)는 상어의 위치, s는 속력, d는 이동 방향, z는 크기이다. d가 1인 경우는 위, 2인 경우는 아래, 3인 경우는 오른쪽, 4인 경우는 왼쪽을 의미한다.문제에서 주어진 조건대로, 절차대로 구현하면 무난하게 구현할 수 있는 문제처럼 보입니다. 다만 문제 조건에서 파생하는 디테일한 세부 조건에 대해서 단번에 고려하기가 무척 어려웠던 문제였습니다. 우선 백본 코드부터 보겠습니다.
import sys
from typing import List, Optional
# input = sys.stdin.readline
input = lambda: sys.stdin.readline().rstrip()
def kill_shark(...):
"""
상어 잡기 연산
"""
pass
def move_shark(...):
"""
상어 이동 연산
"""
pass
R, C, M = map(int, input().rstrip().split())
matrix = [[None for _ in range(C)] for _ in range(R)]
dy = [-1, 1, 0, 0] # <detail 1> 순서대로 상, 하, 우, 좌 임에 유의
dx = [0, 0, 1, -1]
for _ in range(M):
r, c, s, d, z = map(int, input().rstrip().split())
matrix[r-1][c-1] = [s, d-1, z] # 인덱스가 0부터 시작함을 고려
answer = 0
for c in range(C):
answer += kill_shark(matrix, c)
move_shark(matrix)
print(answer)
격자의 크기와 상어의 수를 입력으로 받고, 격자(matrix)를 정의해주었습니다. 이때 격자에서 상어의 정보를 List[int] 형으로 관리할 것이며, 상어가 없는 경우 None으로 표시할 것입니다.
🚨 문제 디테일 1 : 문제에서 1~4 까지 방향 정보를 매핑해서 제공합니다. 이때 저는 주로 방향 정보를 상, 하, 좌, 우 순으로 관리해 활용하는데, 문제에서는 1: 상, 2: 하, 3: 우, 4: 좌 임에 유의해야 합니다.
이후 M마리 상어 정보를 받아 matrix에 저장하고, 각 칸마다 상어를 잡고(kill_shark()), 상어를 이동시키는(move_shark()) 연산을 수행해주면 됩니다. 각 메서드를 구현해보겠습니다.
해당 열(c)에 대해서 모든 행(r)을 탐색하며, 처음 만나는 상어를 잡고, 해당 상어의 크기를 반환해주기만 하면 되는 간단한 메서드입니다.
🚨 문제 디테일 2 : 상어를 잡으면, 격자판에서 잡은 상어가 사라집니다.
def kill_shark(matrix: List[List[Optional[List[int]]]], c: int) -> int:
"""
:param matrix : 격자
:param c : 현재 열
c열에서 모든 행(r)을 탐색해 처음 만나는 상어의 크기를 반환한다.
반환 전 상어 정보를 삭제한다.
"""
for r in range(R):
if matrix[r][c]:
result = matrix[r][c][2] # 상어 크기 정보
matrix[r][c] = None # <detail 2> 상어 정보 삭제
return result
return 0 # 상어가 없으면 0 반환
상어를 잡은 이후엔 상어가 움직이는 연산을 수행해야 합니다. 해당 메서드에서 주의해야할 디테일이 많습니다.
우선, 격자 내 모든 칸을 탐색하며, 상어가 있는 곳을 찾습니다. 상어를 찾았다면 해당 좌표의 상어 정보(속력, 방향)을 고려해 해당 상어를 이동시킵니다. 이 과정까지의 문제 디테일은 아래와 같습니다.
🚨 문제 디테일 3 : 상어가 이동을 마친 후에 한 칸에 상어가 두 마리 이상 있을 수 있고, 이때는 크기가 가장 큰 상어가 나머지 상어를 모두 잡아먹습니다.
이를 위해서 바로 원본 격자에 이동 후 상어 정보를 삽입하면 안됩니다. 모든 상어는 동시에 움직이므로, 모든 상어의 이동 후에 동일 칸의 상어들에 대해 비교를 통해 가장 큰 상어만을 원본 격자에 반영해주어야 합니다. 이를 위해 보조 격자(tmp)를 활용해 이동 후 특정 칸의 가장 큰 상어 정보를 관리해주었습니다.
🚨 문제 디테일 4 : 격자판의 경계를 넘는 경우에는 방향을 반대로 바꿔서 속력을 유지한채로 이동합니다.
🚨 문제 디테일 5 : 상어는 입력으로 주어진 속도(칸/초)로 이동합니다.
이를 위해서 주어진 속력 만큼 반복문으로 상어를 모두 이동시켜볼 수 있습니다. 그러나, 상어는 최대 10,000마리 주어질 수 있고, 속력은 1,000까지 주어질 수 있습니다. 이 경우 상어를 이동시키는 연산 자체만 10,000,000 회이기 때문에, 최대 100칸의 col을 탐색하며 각 천 만회 이상의 연산을 해야 하니, TLE가 발생할 수밖에 없습니다.
이때, 격자의 크기는 R, C로 100까지인데 속력은 1,000까지 주어짐에 대해서 눈여겨봐야 합니다. 100칸을 1초에 1000번 움직인다면, 반복이 발생할 수 밖에 없습니다. 이 반복을 캐치해서 반복되는 연산을 줄여야 합니다.

따라서 같은 주기가 마다 반복됨을 확인할 수 있습니다. 위 아래로 움직이는 경우에는 마다 반복되겠죠. 이를 통해 상어의 속력에 나머지 연산을 적용해서 반복을 없앨 수 있습니다.
이동이 끝난 상어 정보는 보조격자(tmp)의 해당 좌표에서 크기 비교 후 관리되고 있습니다. 모든 이동이 끝났다면, tmp의 정보를 원본격자(matrix)로 옮겨주어 1회 이동을 마무리합니다.
def move_shark(matrix: List[List[Optional[List[int]]]]) -> None:
"""
:param matrix : 격자
격자 내 모든 상어를 1초동안 움직인다.
"""
tmp = [[None for _ in range(C)] for _ in range(R)] # <detail 3> 이동 후 상어 좌표를 임시 저장할 보조 격자
for r in range(R):
for c in range(C):
if not matrix[r][c]:
continue
s, d, z = matrix[r][c]
matrix[r][c] = None # 기존 상어 자리 비우기
# 상어 좌표 이동
ny, nx = r, c
# <detail 5> 나머지 연산을 통해 반복 제거
iteration = s
# 상하 이동의 경우
if d in (0, 1):
iteration %= 2*(R-1)
# 좌우 이동의 경우
else:
iteration %= 2*(C-1)
# 상어 이동
for _ in range(iteration):
# <detail 4> 격자판의 경계를 넘는 경우 방향을 반대로
if ny == 0 and d == 0:
d = 1
if ny == R-1 and d == 1:
d = 0
if nx == 0 and d == 3:
d = 2
if nx == C-1 and d == 2:
d = 3
ny += dy[d]
nx += dx[d]
# <detail 3> 동일 격자 내에는 가장 큰 상어만 남도록
if not tmp[ny][nx] or tmp[ny][nx] and tmp[ny][nx][2] < z:
tmp[ny][nx] = [s, d, z]
# <detail 3> 이동 후 상어 정보를 원본 격자에 반영
for r in range(R):
for c in range(C):
matrix[r][c] = tmp[r][c]
간단해 보이는 문제였지만 5가지 디테일을 정교하게 반영해야 풀이가 가능했던 문제였습니다. 이를 증명하듯, 난이도도 GOLD 1 수준의 구현 문제임을 확인할 수 있었습니다.
역시 중요한 건 문제의 조건을 잘 보는 것이었습니다. 방향의 정보가 상, 하, 우, 좌 로 주어지는 것이나, 상어가 격자 크기가 100인데 1000번 이동할 수 있는 것이나 모두 조건을 자세히 보지 않으면 고려하기 힘든 조건들입니다.
시간이 들어도, 코드 작성에 들어가기 전 문제의 모든 디테일을 파악하고 docstring으로 정리하는 습관을 들여야겠습니다.
input = lambda: sys.stdin.readline().rstrip()Optional을 통해 None일수도 아닐 수도 있는 형을 표현할 수 있습니다. def foo(a: Optional[int]): -> None 와 같이 사용합니다.🙏 문제 접근 방법 및 코드에 대한 피드백과 질문은 환영입니다!