주뇽's 저장소

백준(BOJ) 그래프순회(BFS) 벽 부수고 이동하기 GOLD3 본문

알고리즘/그래프

백준(BOJ) 그래프순회(BFS) 벽 부수고 이동하기 GOLD3

뎁쭌 2023. 12. 13. 19:37
728x90
반응형

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

 

2206번: 벽 부수고 이동하기

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로

www.acmicpc.net

 

시간 제한메모리 제한제출정답맞힌 사람정답 비율
2 초  192 MB 130213 33916 21211 23.281%

문제

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로로 이동하려 한다. 최단경로는 맵에서 가장 적은 개수의 칸을 지나는 경로를 말하는데, 이때 시작하는 칸과 끝나는 칸도 포함해서 센다.

만약에 이동하는 도중에 한 개의 벽을 부수고 이동하는 것이 좀 더 경로가 짧아진다면, 벽을 한 개 까지 부수고 이동하여도 된다.

한 칸에서 이동할 수 있는 칸은 상하좌우로 인접한 칸이다.

맵이 주어졌을 때, 최단 경로를 구해 내는 프로그램을 작성하시오.

입력

첫째 줄에 N(1 ≤ N ≤ 1,000), M(1 ≤ M ≤ 1,000)이 주어진다. 다음 N개의 줄에 M개의 숫자로 맵이 주어진다. (1, 1)과 (N, M)은 항상 0이라고 가정하자.

출력

첫째 줄에 최단 거리를 출력한다. 불가능할 때는 -1을 출력한다.

 

문제 해결

해당 문제는 가중치가 없는 그래프에서의 최단거리를 구하는  BFS를 이용하여 푸는 그래프탐색 문제이다.

하지만 일반적인 BFS탐색과 약간의 차이점은 바로 벽을 단 1회 부수고 이동할 수 있다는 조건이 추가된 문제이다. 처음 이 문제를 접하였을 때, 시간제한이 2초이고 N값과 M값이 1000으로 완탐으로 한 번 접근해볼까라 생각했다. 모든 벽들을 리스트에 담아두고 리스트를 순회하면서 벽을 1번씩 허물어서 결과값을 저장하는 방식으로 했는데 역시나 시간초과를 피할 수 없었다. 그렇다면 어떤 방식으로 접근할까 고민하다가 3차원 배열을 이용하여 벽을 허물지 않았을 때와 벽을 허물었을 때 2가지로 나눠서 최단경로 맵을 갱신하는 방식을 이용했다.

distance = [[[0]*2 for _ in range(M+1)]for _ in range(N+1)]

위와 같은 3차원 배열을 통해서 

- distance[y][x][0] : 벽을 부수지 않았을 때 최단 경로

- distance[y][x][1] : 벽을 부쉈을 때 최단 경로

 

이후 일반적인 bfs탐색을 진행하는데 이 때 벽을 부수면서 진행하는 방식은 다음과 같다.

- 벽을 부순적이 있다면 => 넘어감

- 벽을 부순적이 없다면 => 임의로 벽을 부수고 진행 

else:#-- 벽인 경우 벽을 1회 부수거나 부수지 않거나 택 1
	if crashed == 1: #--이미 벽이 부서줘 있다면 그냥 지나감
		continue
	else: #--부술 수 있다면 임의로 부숴서 최단거리 갱신
		distance[ny][nx][crashed+1] = distance[y][x][crashed]+1
		q.append((nx, ny, crashed+1))

여기서 임의로 벽을 부수는 행위는 crashed = 1로 완전히 부숴버리는게 아닌 +1를 통해 다른 벽들도 부술 수 있도록 가능성을 남겨두는 것이다. 만약 벽을 부술 수 있다면 crashed = 1로 바꾸는 순간 하나의 벽만 부수고 다음을 진행하지 못한다.

ex)

0 1 0

1 1 0

1 1 0 

 

위와 같은 경우 [1,1] 에서 출발할 때,  [1,2], [2, 1] 모두 가능성을 둬야 하는데 crashed =1로 바꾸면 둘 중 먼저 탐색한 하나만 큐에 삽입이 가능하다. 하지만 crahsed+1로 넣어주면 [1,2], [2,1] 를 탐색하는 과정에서는 벽을 부순적이 없기 때문에 2개의 좌표  모두 큐에 삽입이 가능하다. 위 부분만을 제외하면 일반적인 너비우선 탐색을 진행하면 된다.

import sys
from collections import deque
#sys.stdin = open("3.그래프탐색/input.txt")

steps = [(0, 1), (0, -1), (1, 0), (-1, 0)]
N,M = map(int, sys.stdin.readline().split())
board = [[0 for _ in range(M+1)] for _ in range(N+1)]
distance = [[[0]*2 for _ in range(M+1)]for _ in range(N+1)]

for i in range(1, N+1):
    data = sys.stdin.readline().split()
    for j in range(1, M+1):
        board[i][j] = int(data[0][j-1])

def myprint(string):
    if string == "dis":
        for h in range(2):
            for i in range(1, N+1):
                for j in range(1, M+1):
                    print(distance[i][j][h], end =' ')
                print()
            print()
    else:
        for i in range(1, N+1):
            for j in range(1, M+1):
                print(board[i][j], end=' ')
            print()

myprint('dis')
def bfs():
    q = deque()
    q.append((1, 1, 0))
    distance[1][1][0] = 1
    
    while q:
        x, y, crashed = q.popleft()
        if x == M and y == N:
            return distance[y][x][crashed]
        for step in steps:
            nx = x + step[0]
            ny = y + step[1]
            if 1<=nx<=M and 1<=ny<=N and distance[ny][nx][crashed] == 0:
                if board[ny][nx] == 0: #-- 갈 수 있는 곳이면 감
                    distance[ny][nx][crashed] = distance[y][x][crashed]+1
                    q.append((nx, ny, crashed))
                else:#-- 벽인 경우 벽을 1회 부수거나 부수지 않거나 택 1
                    if crashed == 1: #--이미 벽이 부서줘 있다면 그냥 지나감
                        continue
                    else: #--부술 수 있다면 임의로 부숴서 최단거리 갱신
                        distance[ny][nx][crashed+1] = distance[y][x][crashed]+1
                        q.append((nx, ny, crashed+1))
    return -1            
                        
ans = bfs()
print(ans)