Notice
Recent Posts
Recent Comments
Today
Total
04-25 03:18
Archives
관리 메뉴

Jeongchul Kim

백준 삼성 코딩 기출 문제 - 구슬 탈출 python 본문

Algorithm

백준 삼성 코딩 기출 문제 - 구슬 탈출 python

김 정출 2019. 10. 16. 16:52

백준 삼성 코딩 기출 문제 - 구슬 탈출 python



출처 : BAEKJOON ONLINE JUDGE

구슬 탈출(https://www.acmicpc.net/problem/13459)


문제 설명

보드가 N X M 크기로 있고, 중력을 통해서 떨어지기 때문에 기울이는 것을 통해서 위/아래/오른쪽/왼쪽으로 움직임이 가능합니다. 빨간 구슬(R)을 구멍(O)으로 움직여야 하고, 단 파란 구슬(B)가 구멍(O)으로 들어가거나 또는 같이 들어가도 실패합니다. 움직이는 횟수는 총 10번 이하로 제한되고 가능하면 1, 실패하면 0을 출력합니다.


문제 풀이

BFS(Breadth-First-Search) 알고리즘은  queue(FIFO -> deque사용)과 while(무한 루프)를 사용해 탐색을 구현합니다. DFS(Depth First Search) 알고리즘은 stack(LIFO)과 재귀 함수 사용해 구현해야 합니다.


여기서는 BFS 알고리즘을 통해서 탐색을 진행합니다. 

[1] 전체 행렬의 row에 해당하는 n, column의 크기는 m 그리고 보드인 board 입력을 받습니다.
- from sys import stdin을 이용해서 입력을 받습니다.

from sys import stdin
from collections import deque

input = stdin.readline

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

 

 - board를 출력하면 다음과 같은 형태로 저장됩니다.


[2] 빨간 구슬, 파란 구슬 2개가 동시에 움직이므로, 각 2개의 구슬 x, y좌표를 visited 배열에 4차원으로 선언하고 False로 초기화합니다. 그리고 while문을 통해 bfs 탐색을 진행하면서 방문 여부를 확인하고 방문을 안했다면 queue에 넣고, True로 체크하게 됩니다.


visited = [[[[False]*m for _ in range(n)] for _ in range(m)] for _ in range(n)]


[3] 구슬이 움직일 수 있는 x, y의 좌표를 만들고, collection의 deque를 이용해 queue를 생성합니다.

from collections import deque
dx, dy = (-1, 0, 1, 0), (0, 1, 0, -1) # 각각 왼쪽 / 위쪽 / 오른쪽 / 아래쪽 x,y 좌표를 의미한다.
q = deque()


[4] 보드에서 빨간 구슬(R)과 파란 구슬(B)가 어느 x, y 좌표에 있는지가 중요합니다. 
- 문자열 비교를 통해 x, y 위치를 가져오고, queue에 넣을 때는 현재 몇 번 구슬을 움직 였는 지에 대한 정보를 저장하는 depth를 저장합니다. depth는 1로 초기화합니다.
- 따라서 큐에는 (빨간 구슬 x, y, 파란 구슬 x, y, 몇 번 구슬 움직였는지 )가 들어가게 되죠. 또한 visited 배열에 해당 구슬 정보를 True로 넣고 값을 업데이트합니다.


def init():
    rx, ry, bx, by = [0] * 4 # 초기화 0, 0, 0, 0
    for i in range(n):
        for j in range(m):
            if board[i][j] == 'R': # board에 빨간 구슬이라면 좌표 값 저장
                rx, ry = i, j
            elif board[i][j] == 'B': # board에 파란 구슬이라면 좌표 값 저장
                bx, by = i, j
    print(rx, ry, bx, by)
    q.append((rx, ry, bx, by, 1)) # 위치 정보와 depth
    visited[rx][ry][bx][by] = True


[5] 다음은 구슬이 움직임이 가능하기 때문에 move라는 함수로 구현을 합니다.
- 벽(#) 또는 구멍(O)이 아닐 때 까지 정해진 방향으로 전진합니다.

 - 여기서 이동한 칸 수를 저장하는 count라는 변수가 있습니다. 이 변수는 depth와는 전혀 상관이 없고, 빨간 구슬과 파란 구슬이 겹쳐서는 안된다라는 조건을 풀기 위해 쓰이는 변수입니다.

 - 같은 방향으로 움직이는 가운데 2개의 구슬이 겹친다는 조건이면 둘 중 하나의 구슬의 움직이는 칸 수를 하나 줄여야 됩니다. 따라서 같이 움직이지만 서로 겹치지 않겠죠. 움직임이 가장 많은 구슬이 한 칸 적어야할 것입니다. 이를 판단하기 위해 count에 움직인 칸 수를 저장합니다.


def move(x, y, dx, dy):
    count = 0 # 이동한 칸 수
    # 다음 이동이 벽이거나 구멍일 때까지
    while board[x+dx][y+dy] != '#' and board[x][y] != 'O':
        x += dx
        y += dy
        count = 1
    return x, y, count


[6] 핵심인 bfs 탐색입니다.
- 큰 구조로는 초기화를 하고, queue에 값이 없을 때 까지 while문을 돌게 됩니다.

 - while 문 안에서는 popleft()를 통해서 FIFO 구조에 따라 처음에 위치한 값을 가지고 옵니다. 

 - 10번 이하여야 한다는 조건을 먼저 봅니다. 넘어서면 break로 while문을 종료해버립니다.

 - 다음으로는 4방향으로 움직일 수 있는지 시도를 해야 합니다. move를 써서 이동합니다.
- 이동한 x, y좌표 값을 토대로 판단을 진행합니다. 파란 구슬(B)이 구멍(O)에 떨어졌다면 실패이므로 continue문으로 넘어갑니다.
- 위의 조건을 지나면, 파란 구슬이 안떨어진 경우이며, 빨간 구슬(R)이 구멍(O)에 떨어졌다면 성공이므로 print(1)을 하고 return 문으로 반복문을 종료 함수의 결과값을 리턴합니다.
- 만약에 빨간 구슬과 파란 구슬이 겹쳐진다면, count(구슬이 움직인 칸 수)를 기반으로 많이 움직인 구슬을 현재 방향 기준으로 뒤로 한칸 물립니다.
- 마지막은 방문 여부를 확인합니다. 방문하지 않았다면 visited 배열에서 True로 체크하고 queue에 넣습니다.

 - 10번을 넘어 버리면 실패이므로 print(0)을 출력하고 마칩니다.


def bfs():
    init()
    while q: # BFS -> queue, while
        rx, ry, bx, by, depth = q.popleft()
        if depth > 10: # 10 이하여야 한다.
            break
        for i in range(len(dx)): # 4방향으로 시도한다.
            next_rx, next_ry, r_count = move(rx, ry, dx[i], dy[i]) # RED
            next_bx, next_by, b_count = move(bx, by, dx[i], dy[i]) # BLUE
           
            print("depth : ", depth, "direction : ", dx[i], dy[i], 'RED', next_rx, next_ry, 'BLUE', next_bx, next_by)
           
            if board[next_bx][next_by] == 'O': # 파란 구슬이 구멍에 떨어지지 않으면(실패 X)
                continue
            if board[next_rx][next_ry] == 'O': # 빨간 구슬이 구멍에 떨어진다면(성공)
                print(1)
                return
            if next_rx == next_bx and next_ry == next_by : # 빨간 구슬과 파란 구슬이 동시에 같은 칸에 있을 수 없다.
                if r_count > b_count: # 이동 거리가 많은 구슬을 한칸 뒤로
                    next_rx -= dx[i]
                    next_ry -= dy[i]
                else:
                    next_bx -= dx[i]
                    next_by -= dy[i]
            # BFS 탐색을 마치고, 방문 여부 확인
            if not visited[next_rx][next_ry][next_bx][next_by]:
                visited[next_rx][next_ry][next_bx][next_by] = True
                q.append((next_rx, next_ry, next_bx, next_by, depth +1))
    print(0)


첫 번째 테스트 케이스를 보죠. 


보는 입장에서는 빨간 구실이 오른쪽으로 한 칸으로 이동하면 성공입니다.

배열로 구성된 보드를 움직이려면 row 인덱스에 해당하는 0이고, column 인덱스에 해당하는 1이면 오른쪽으로 움직입니다. x, y 좌표계에 따라서 x로 +1이동하면 되는거 아닌가.. 헷갈립니다. 그런 경우를 방지하고자 row와 column 인덱스를 생각해보세요. 

 - row 인덱스는 0번째 인덱스로 시작해 +1이면 아래로, -1이면 위로 움직입니다. 

 - column 인덱스는 0번째 인덱스로 시작해 오른쪽으로는 +1 왼쪽으로는 -1을 해야 움직입니다.


2번째 케이스도 비슷합니다.


전체 코드

from sys import stdin
from collections import deque

input = stdin.readline

n, m = map(int, input().split())
board = [list(input().strip()) for _ in range(n)]
visited = [[[[False]*m for _ in range(n)] for _ in range(m)] for _ in range(n)]
dx, dy = (-1, 0, 1, 0), (0, 1, 0, -1)
q = deque()

def init():
    rx, ry, bx, by = [0] * 4 # 초기화 0, 0, 0, 0
    for i in range(n):
        for j in range(m):
            if board[i][j] == 'R': # board에 빨간 구슬이라면 좌표 값 저장
                rx, ry = i, j
            elif board[i][j] == 'B': # board에 파란 구슬이라면 좌표 값 저장
                bx, by = i, j
    q.append((rx, ry, bx, by, 1)) # 위치 정보와 depth
    visited[rx][ry][bx][by] = True
   
def move(x, y, dx, dy):
    count = 0 # 이동한 칸 수
    # 다음 이동이 벽이거나 구멍이 아닐 때까지
    while board[x+dx][y+dy] != '#' and board[x][y] != 'O':
        x += dx
        y += dy
        count += 1
    return x, y, count


def bfs():
    init()
    while q: # BFS -> queue, while
        rx, ry, bx, by, depth = q.popleft() # FIFO
        if depth > 10: # 10 이하여야 한다.
            break
        for i in range(len(dx)): # 4방향으로 시도한다.
            next_rx, next_ry, r_count = move(rx, ry, dx[i], dy[i]) # RED
            next_bx, next_by, b_count = move(bx, by, dx[i], dy[i]) # BLUE
                       
            if board[next_bx][next_by] == 'O': # 파란 구슬이 구멍에 떨어지지 않으면(실패 X)
                continue
            if board[next_rx][next_ry] == 'O': # 빨간 구슬이 구멍에 떨어진다면(성공)
                print(1)
                return
            if next_rx == next_bx and next_ry == next_by : # 빨간 구슬과 파란 구슬이 동시에 같은 칸에 있을 수 없다.
                if r_count > b_count: # 이동 거리가 많은 구슬을 한칸 뒤로
                    next_rx -= dx[i]
                    next_ry -= dy[i]
                else:
                    next_bx -= dx[i]
                    next_by -= dy[i]
            # BFS 탐색을 마치고, 방문 여부 확인
            if not visited[next_rx][next_ry][next_bx][next_by]:
                visited[next_rx][next_ry][next_bx][next_by] = True
                q.append((next_rx, next_ry, next_bx, next_by, depth +1))
    print(0) # 실패

bfs()











Comments