문제 출처 및 풀이 코드
(Backjoon 13460) 구슬 탈출 2
깃헙 코드
문제 요약
- 테두리를 포함 10x10의 크기의 보드가 존재하며 특정 위치에 공이 빠지는 구멍이 존재한다.
- 보드 내부에는 적색 공과 청색 공이 있는 데, 청색 공을 빼지 않고 적색 공을 빼야한다.
- 이를 위해 취할 수 있는 액션은 보드를 상하좌우로 기울기이다.
- 한번 보드를 기울이면 구슬의 움직임이 멈출 때까지 기울여야한다.
- 보드는 최대 10번까지 기울일 수 있다.
- 최소 몇 번 기울이면 적색공을 꺼낼 수 있는지를 묻는 문제.
- 만약 10회 이하로 꺼내지 못하면 -1을 반환 해야한다.
문제 풀이
- 우선 문제에 어느 정도 힌트가 존재한다. 10회 이하로 꺼내라는 말이 그것.
- 이 문제는 적색 공이 목적지로 최단 루트로 이동하는 것을 계산해야하긴 하지만 청색 공으로 인해 문제가 해소되는 경우도 존재하고, 문제를 해소할 수 없는 경우도 존재하기 때문에 단순히 적색 공으로만 문제를 풀려고 하면 안된다.
- 청색 공의 존재 인해로 문제를 풀 수 있는 가장 단순한 케이스
- 위의 문제에서는 B가 오른쪽 구석에 위치하여 R이 그것을 토대로 그 다음 위치에 존재해야 O에 들어갈 수 있다.
- 횟수의 제한이 주어지고, 고려해야할 요소가 두 개 이상이므로 하나의 최선의 루트를 찾는 것보단 모든 경우의 수를 따져봐야한다.
- 적색 공의 최단 루트를 찾는 모든 경우의 수를 고려해야하므로 여기에서 사용해야할 알고리즘은 BFS이다.
코드
from collections import deque
RY, RX, BY, BX = 0, 1, 2, 3
def find_RandB(board):
ball = [-1, -1, -1, -1]
for y, line in enumerate(board):
if 'R' in line:
x = line.index('R')
ball[:2] = [y, x]
board[y][x] = '.'
if 'B' in line:
x = line.index('B')
ball[2:] = [y, x]
board[y][x] = '.'
return ball
def toward(board, ball, direction):
board[ball[RY]][ball[RX]] = 'R'
board[ball[BY]][ball[BX]] = 'B'
dy, dx = direction
h, w = len(board), len(board[0])
red_on_hole = False
blue_on_hole = False
def dot(x, y): return - x[0]*y[0] - x[1]*y[1]
ball_order = [[RY, RX], [BY, BX]] if dot(ball[:2], direction) < dot(
ball[2:], direction) else [[BY, BX], [RY, RX]]
moved = False
for Y, X in ball_order:
while True:
y, x = ball[Y], ball[X]
if (0 < y+dy) and (y+dy < h-1) and (0 < x+dx) and (x+dx < w - 1):
next_point = board[y+dy][x+dx]
if next_point == 'O':
if Y == RY:
red_on_hole = True
board[ball[RY]][ball[RX]] = '.'
break
else:
blue_on_hole = True
break
elif next_point == '.':
moved = True
board[y][x], board[y+dy][x +
dx] = board[y+dy][x+dx], board[y][x]
ball[Y], ball[X] = y+dy, x+dx
else:
break
else:
break
if red_on_hole == False:
board[ball[RY]][ball[RX]] = '.'
board[ball[BY]][ball[BX]] = '.'
return ball, red_on_hole, blue_on_hole, moved
def bfs(board, ball_init):
q = deque()
visited = []
q.append([ball_init, 0])
dir = [(1, 0), (-1, 0), (0, 1), (0, -1)]
while q:
ball, cnt = q.popleft()
for d in dir:
ball_next, red_on_hole, blue_on_hole, moved = toward(
board, ball.copy(), d)
if blue_on_hole:
continue
if red_on_hole:
return cnt + 1
if moved and (ball_next not in visited):
if cnt + 1 < 10:
q.append([ball_next, cnt+1])
visited.append(ball_next)
else:
return -1
n, m = map(int, input().split())
board = [list(input()) for _ in range(n)]
ball_init = find_RandB(board)
print(bfs(board, ball_init))
이번에 새로 배운 것
map(func, iterable)
- visited나 queue에 저장할 ball이 서로 다른 값을 갖기 위해서는 가급적이면 내부에 갖는 요소들을 기본 자료형으로 쓰기
- 처음에는 ball을 dictionary로 만들고 내부 좌표를 list로 만들었는 데 copy하였을 때 내부 요소들은 copy 되지 않고 reference로 받아서 queue에 들어간 좌표들이나 visited에 들어간 ball 좌표들이 모두 바뀌어서 문제가 생겼다.