3 minute read

백준 문제 중 13460번

https://www.acmicpc.net/problem/13460

문제

스타트링크에서 판매하는 어린이용 장난감 중에서 가장 인기가 많은 제품은 구슬 탈출이다. 구슬 탈출은 직사각형 보드에 빨간 구슬과 파란 구슬을 하나씩 넣은 다음, 빨간 구슬을 구멍을 통해 빼내는 게임이다.

보드의 세로 크기는 N, 가로 크기는 M이고, 편의상 1×1크기의 칸으로 나누어져 있다. 가장 바깥 행과 열은 모두 막혀져 있고, 보드에는 구멍이 하나 있다. 빨간 구슬과 파란 구슬의 크기는 보드에서 1×1크기의 칸을 가득 채우는 사이즈이고, 각각 하나씩 들어가 있다. 게임의 목표는 빨간 구슬을 구멍을 통해서 빼내는 것이다. 이때, 파란 구슬이 구멍에 들어가면 안 된다.

이때, 구슬을 손으로 건드릴 수는 없고, 중력을 이용해서 이리 저리 굴려야 한다. 왼쪽으로 기울이기, 오른쪽으로 기울이기, 위쪽으로 기울이기, 아래쪽으로 기울이기와 같은 네 가지 동작이 가능하다.

각각의 동작에서 공은 동시에 움직인다. 빨간 구슬이 구멍에 빠지면 성공이지만, 파란 구슬이 구멍에 빠지면 실패이다. 빨간 구슬과 파란 구슬이 동시에 구멍에 빠져도 실패이다. 빨간 구슬과 파란 구슬은 동시에 같은 칸에 있을 수 없다. 또, 빨간 구슬과 파란 구슬의 크기는 한 칸을 모두 차지한다. 기울이는 동작을 그만하는 것은 더 이상 구슬이 움직이지 않을 때 까지이다.

보드의 상태가 주어졌을 때, 최소 몇 번 만에 빨간 구슬을 구멍을 통해 빼낼 수 있는지 구하는 프로그램을 작성하시오.

입력

첫 번째 줄에는 보드의 세로, 가로 크기를 의미하는 두 정수 N, M (3 ≤ N, M ≤ 10)이 주어진다. 다음 N개의 줄에 보드의 모양을 나타내는 길이 M의 문자열이 주어진다. 이 문자열은 ‘.’, ‘#’, ‘O’, ‘R’, ‘B’ 로 이루어져 있다. ‘.’은 빈 칸을 의미하고, ‘#’은 공이 이동할 수 없는 장애물 또는 벽을 의미하며, ‘O’는 구멍의 위치를 의미한다. ‘R’은 빨간 구슬의 위치, ‘B’는 파란 구슬의 위치이다.

입력되는 모든 보드의 가장자리에는 모두 ‘#’이 있다. 구멍의 개수는 한 개 이며, 빨간 구슬과 파란 구슬은 항상 1개가 주어진다.

출력

최소 몇 번 만에 빨간 구슬을 구멍을 통해 빼낼 수 있는지 출력한다. 만약, 10번 이하로 움직여서 빨간 구슬을 구멍을 통해 빼낼 수 없으면 -1을 출력한다.


풀이

상당히 구현하기 힘들었던 문제였다.

문제에서 약간 애매해서 헷갈렸던 부분이 있었는데 만약 왼쪽으로 기울이고 벽까지 가기전에 수평을 맞추는게 경우가 가능한지가 조금 헷갈렸다.

그러나 문제를 잘 읽어보면 수평을 맞추는 동작은 없고 무조건 네방향 중 한곳으로 기울이는 것만 가능하며, 만약 왼쪽으로 기울인 후 바로 오른쪽으로 기울이는건 최소값을 구할때 의미 없는 동작이라는걸 알 수있다. 그러므로 벽을 만나거나 구멍을 만날때까지 정한 방향으로만 가게끔 생각하면 된다.

최단거리를 찾는 문제이므로 큐를 통한 BFS 탐색을 떠올리고 문제에 접근했다.

전체적인 흐름은 다음과 같다.

  1. 시작점에서 빨간 구슬 파란 구슬 각각을 네 방향 중 한가지 방향으로 벽을만나거나 구멍을 만나기 전까지 쭉 굴린다.
  2. 각 구슬이 굴러서 도달한 위치가 각각 파란구슬은 ‘O’가 아니고 빨간구슬은 ‘O’이 되었거나, 굴린 횟수가 10번이상이 된다면 탐색을 종료한다.
  3. 종료조건을 만족하지 못했다면 다음 탐색을 위해 큐에 각 구슬의 현재 위치를 다음 탐색의 시작 위치로 넣는데, 넣기전에 현 위치가 이미 방문했던 상태인지 방문 여부를 확인하고 방문하지 않았다면 큐에 넣는다.
from collections import deque
from typing import List, Tuple
from pprint import pprint

n, m = map(int, input().split())
board = []
for _ in range(n):
    board.append(list(input()))

for row in range(1, n-1):
    for col in range(1, m-1):
        if board[row][col] == 'R':
            red = [row, col]
            continue
        if board[row][col] == 'B':
            blue = [row, col]
            continue

dr, dc = [1, -1, 0, 0], [0, 0, 1, -1]


def move(row:int, col:int, dir:int)->Tuple[int]:

     # 두 구슬의 위치가 같을 때 처리를 위한 변수 cnt
    cnt = 0

     # 더 이상 굴릴 수 없을 때까지 dr[dir] 방향으로 이동
    while board[row + dr[dir]][col + dc[dir]] != '#'\
      and board[row][col] != 'O':
        row += dr[dir]
        col += dc[dir]
        cnt += 1
    return row, col, cnt


def bfs(red:List[int], blue:List[int])->int:

     # [red_row][red_col][blue_row][blue_col]의 방문여부를 저장할 배열 선언
    visited = [[[[False for _ in range(m)]
                            for _ in range(n)]
                                for _ in range(m)] 
                                    for _ in range(n)]
    
    q = deque()
    q.append([red[0], red[1], blue[0], blue[1], 1])   
    
    while q:
         # r_r, r_c 는 빨간구슬의 좌표
         # b_r, b_c 는 파란구슬의 좌표
         # cnt는 이동횟수
        r_r, r_c, b_r, b_c, cnt = q.popleft()
        visited[r_r][r_c][b_r][b_c] = True

        if cnt>10:
            return -1

        for dir in range(4):
            nr_r, nr_c, rcnt = move(r_r, r_c, dir)
            nb_r, nb_c, bcnt = move(b_r, b_c, dir)
  
             # 파란색이 빠져나가지 않았을 때만 고려           
            if board[nb_r][nb_c] != 'O':

                 # 빨간구슬만 빠져나간 경우 cnt return  
                if board[nr_r][nr_c] == 'O':
                    return cnt

                 # 두 구슬이 같을 위치에 있을 때 처리
                if nr_r == nb_r and nr_c == nb_c:
                     # 두 구슬 중 이동횟수가 더 큰 구슬을 재조정     
                    if rcnt > bcnt:
                        nr_r -= dr[dir] 
                        nr_c -= dc[dir]
                    else:
                        nb_r -= dr[dir]
                        nb_c -= dc[dir]
                    
                if not visited[nr_r][nr_c][nb_r][nb_c]:
                    visited[nr_r][nr_c][nb_r][nb_c] = True
                    q.append([nr_r, nr_c, nb_r, nb_c, cnt + 1])
    else:
        return -1
print(bfs(red,blue))
10 10
##########
#R#...##B#
#...#.##.#
#####.##.#
#......#.#
#.######.#
#.#....#.#
#.#.##...#
#O..#....#
##########
7

배운점

꼼꼼함이 매우 필요한 문제였다. 예외처리나 구슬의 위치가 같을때를 처리하는데 상당히 오래걸렸었다.

Leave a comment