https://www.acmicpc.net/problem/9944
9944번: NxM 보드 완주하기
N×M 보드 위에서 할 수 있는 게임이 있다. 보드는 크기가 1×1인 정사각형 칸으로 나누어져 있다. 보드의 각 칸은 빈 칸 또는 장애물이다. 장애물은 아래 그림에선 어두운 사각형으로 표시되어져
www.acmicpc.net
0. 문제
N×M 보드 위에서 공을 가지고 할 수 있는 게임이 있다. 공은 다음과 같은 방식으로 1단계씩 움직인다.
- 위, 아래, 오른쪽, 왼쪽 중 방향 하나를 고른 다음, 그 방향으로 공을 계속해서 이동시킨다.
- 공은 장애물, 보드의 경계, 이미 공이 지나갔던 칸을 만나기 전까지 계속해서 이동한다.
게임은 공이 더 이상 이동할 수 없을 때 끝난다. 이 때, 모든 빈 칸을 공이 방문한 적이 있어야 한다.
모든 빈 칸을 방문할 수 없다면 최소 이동 횟수는 -1이다. 가능한 이동 경로의 수는 1,000,000개를 넘지 않는다.
1. 아이디어
문제를 풀기 위해 다음과 같은 요소를 생각해야 한다.
- 한 지점에서 공이 어느 방향으로 이동해야 하는가?
- 알 수 없다. 상, 하, 좌, 우 모든 방향으로 이동하는 것을 고려해야 한다.
- 공은 어디에서 출발해야 하는가?
- 특정 위치에서만 출발해도 되는 경우 대개 구석(edge) 위치이다. 하지만 해당 경우 모든 케이스를 커버하지 못한다.
- 따라서 모든 위치에서 출발하는 경우를 살펴보아야 한다.
이제 공이 이동하는 방향과 시작점을 정할 수 있게 되었으므로 DFS 탐색을 하면 된다.
2. 구현
# 위 왼 아래 오
dr = [-1, 0, 1, 0]
dc = [0, -1, 0, 1]
def go_or_back(start, d, flag, end=None):
nr, nc = start
next_start = (0, 0)
# 범위 안에 있으면
while 0 <= nr < R and 0 <= nc < C:
# flag = 1 (go): 이동할 수 있으면 방문 체크
if flag and (IN[nr][nc] == '.'):
IN[nr][nc] = 'O'
next_start = (nr, nc)
# flag = 0 (back): 이미 방문 체크 되어있으면 방문 풀기
elif (not flag) and (IN[nr][nc] == 'O'):
IN[nr][nc] = '.'
else:
break
# end 지점에 도달했다면 종료
if (nr, nc) == end:
break
# 다음 위치
nr += dr[d]
nc += dc[d]
return next_start
def func(start, count):
global result
# 재귀
r, c = start
can_go = 0
for d in range(4):
nr = r + dr[d]
nc = c + dc[d]
# 해당 방향으로 갈 수 있으면 쭉 이동하기
if 0 <= nr < R and 0 <= nc < C and IN[nr][nc] == '.':
can_go = 1
# (nr, nc) 부터 쭉 이동하면서 방문체크 => next_start: 다음 시작점
next_start = go_or_back((nr, nc), d, 1)
func(next_start, count + 1)
# 재귀 나온 후 next_start 부터 (nr, nc)까지 방문 해제 (방향은 반대 방향)
go_or_back(next_start, (d + 2) % 4, 0, (nr, nc))
# 종료 조건 => 갈 수 있는 곳이 없는 상태
if not can_go:
# 모든 칸을 방문했는지 확인
board_count = 0
for i in range(R):
for j in range(C):
board_count += 1 if IN[i][j] == 'O' else 0
# board 칸을 모두 방문했다면 result 갱신
if board_count == total_board_count:
result = min(result, count)
count = 0
while 1:
count += 1
try:
R, C = map(int, input().split())
IN = [list(input()) for _ in range(R)]
# board 칸의 개수 구하기
total_board_count = 0
for r in range(R):
for c in range(C):
total_board_count += 1 if IN[r][c] == '.' else 0
# 함수 시작 => 모든 위치에서 함수 실행하여 확인하기
result = 99999999999999999
for r in range(R):
for c in range(C):
if IN[r][c] == '.':
IN[r][c] = 'O'
func((r, c), 0)
IN[r][c] = '.'
# 모든 빈 칸을 방문할 수 있는 경우가 없다면 -1
if result == 99999999999999999:
result = -1
print(f'Case {count}: {result}')
except:
break
'APS > BOJ' 카테고리의 다른 글
[Python / BOJ 1248] Guess (0) | 2022.08.30 |
---|---|
[Python / BOJ 1941] 소문난 칠공주 (0) | 2022.08.30 |
[Python / BOJ 2661] 좋은 수열 (0) | 2022.08.04 |
[Python / BOJ 2239] 스토쿠 (0) | 2022.07.29 |
[Python / BOJ 20440] 니가 싫어 싫어 너무 싫어 싫어 오지 마 내게 찝쩍대지마 (0) | 2022.06.19 |