시뮬레이션, BFS - 미지의 공간 탈출

jisu_log·2025년 9월 8일

알고리즘 문제풀이

목록 보기
98/105

  • get_exit()로 시간의 벽의 출구의 방향 및 좌표를 얻고, 출구가 있는 면엔 [0,1,1,...], 나머지 면엔 [1,1,...]를 M번째 행으로 추가하기
  • warp(nr,nc,side)로 경계 이탈 시 면 이동 및 좌표 변환하기, 경계 이탈하지 않으면 그대로 (nr,nc,side) 반환하기(M번째 행 추가한 맵의 인덱스 체크 주의)
  • 이상현상이 언제 해당 칸으로 확산되는지를 미리 계산해서 맵에 기록해두기
  • 미지의 공간 BFS: 시작점 (s_start_r,s_start_c)에서 space_maps를 따라 확장하되, 해당 칸의 이상현상 확산시간 num에 대해 T+turn+1 < num일 때만 지나갈 수 있도록 구현하기(타임머신이 지나가기 직전에 이상현상이 먼저 확산됨 주의!)
from collections import deque

N, M, F = map(int, input().split())
space_maps = []

# 미지의 공간 평면도
for i in range(N):
    line = list(map(int, input().split()))
    for j in range(N):
        # 탈출구 -1로 표기 변경 (맵에 이상현상의 확산 시간을 양수로 표기할 것이므로)
        if line[j] == 4:
            line[j] = -1
    space_maps.append(line)

# 시간의 벽의 각 면의 2차원 맵
time_east = []
time_west = []
time_south = []
time_north = []
time_top = []

TOP, NORTH, EAST, SOUTH, WEST = 0, 1, 2, 3, 4

start_r, start_c = -1, -1

for j in range(M):
    line = list(map(int, input().split()))
    time_east.append(line)
for j in range(M):
    line = list(map(int, input().split()))
    time_west.append(line)
for j in range(M):
    line = list(map(int, input().split()))
    time_south.append(line)
for j in range(M):
    line = list(map(int, input().split()))
    time_north.append(line)
for j in range(M):
    line = list(map(int, input().split()))
    for l in range(M):
        # 타임머신 시작 좌표 저장
        if line[l] == 2:
            start_r, start_c = j, l

    time_top.append(line)

time_maps = []
time_maps.extend([time_top, time_north, time_east, time_south, time_west])


# 이상 현상 저장 리스트
strange = []

for i in range(F):
    line = list(map(int, input().split()))
    strange.append(line)


# 시간의 벽의 단 한칸의 출구의 방향 및 좌표 구하기
def get_exit():

    for i in range(N):
        for j in range(N):
            if space_maps[i][j] == 3:
                # 북쪽에 출구
                for k in range(M):
                    if 0 <= i - 1 < N and 0 <= j + k < N and space_maps[i - 1][j + k] == 0:
                        return 1, M - 1 - k, i - 1, j + k
                # 서쪽에 출구
                for k in range(M):
                    if 0 <= i + k < N and 0 <= j - 1 < N and space_maps[i + k][j - 1] == 0:
                        return 4, k, i + k, j - 1
                # 동쪽에 출구
                for k in range(M):
                    if 0 <= i + k < N and 0 <= j + M < N and space_maps[i + k][j + M] == 0:
                        return 2, M - 1 - k, i + k, j + M
                # 남쪽에 출구
                for k in range(M):
                    if 0 <= i + M < N and 0 <= j + k < N and space_maps[i + M][j + k] == 0:
                        return 3, k, i + M, j + k

# 시간의 벽 탈출한 후 시작점 좌표
s_start_r, s_start_c = -1, -1
exit_dir, k, s_start_r, s_start_c = get_exit()

others = [] # 출구를 가지지 않는 면들

# 시간의 벽의 출구(0)와 장애물인 곳(1)을 시간의 벽 맵에 표시해주기
line = []
for i in range(M):
    if i == k:
        line.append(0)
    else:
        line.append(1)

time_maps[exit_dir].append(line)


# 시간의 벽 출구가 없는 면들에는 1로 채워진 리스트 추가해주기(BFS에서 아래로 더 나아갈 수 없도록)
for i in range(1, 5):
    if i == exit_dir:
        continue

    line = []
    for j in range(M):
        line.append(1)

    time_maps[i].append(line)


# 0: top, 1: north, 2: east, 3: south, 4: west

dx = [0, 0, 1, -1] # 동, 서, 남, 북 순
dy = [1, -1, 0, 0]



# 시간의 벽에서의 탈출 경로 탐색 BFS
def bfs_time_space():

    visited = [set(), set(), set(), set(), set()]

    q = deque()
    q.append((start_r, start_c, 0, 0))
    visited[0].add((start_r, start_c))

    min_turn = -1

    # 시간의 벽의 다른 면으로 좌표 변환 처리 함수
    def warp(nr, nc, side):
        # warp 해당 사항 없다면 기존 그대로 리턴
        if side == 0 and 0 <= nr < M and 0 <= nc < M:
            return nr, nc, side
        elif 1 <= side <= 4 and 0 <= nr <= M and 0 <= nc < M:
            return nr, nc, side

        if side == TOP:
            if nr < 0:
                return 0, M - 1 - nc, NORTH
            elif nr > M - 1:
                return 0, nc, SOUTH
            elif nc < 0:
                return 0, nr, WEST
            elif nc > M - 1:
                return 0, M - 1 - nr, EAST
            
        
        elif side == NORTH:
            if nr < 0:
                return 0, M - 1 - nc, TOP
            elif nc < 0:
                return nr, M - 1, EAST
            elif nc > M - 1:
                return nr, 0, WEST
            
        elif side == EAST:
            if nr < 0:
                return M - 1 - nc, M - 1, TOP
            elif nc < 0:
                return nr, M - 1, SOUTH
            elif nc > M - 1:
                return nr, 0, NORTH
            
        elif side == SOUTH:
            if nr < 0:
                return M - 1, nc, TOP
            elif nc < 0:
                return nr, M - 1, WEST
            elif nc > M - 1:
                return nr, 0, EAST
            
        elif side == WEST:
            if nr < 0:
                return nc, 0, TOP
            elif nc < 0:
                return nr, M - 1, NORTH
            elif nc > M - 1:
                return nr, 0, SOUTH
        


    while q:

        r, c, side, turn = q.popleft()

        if r == M:
            min_turn = turn

            break


        for i in range(4):
            nr, nc = r + dx[i], c + dy[i]

            # 동,서,남,북 면에 있을 때 nr이 M 초과 시 맵 밖이므로 패스
            if 1 <= side <= 4 and nr > M:
                continue

            # 좌표 warp
            n_r, n_c, new_side = warp(nr, nc, side)

            if (n_r, n_c) not in visited[new_side] and time_maps[new_side][n_r][n_c] == 0:
                q.append((n_r, n_c, new_side, turn + 1))
                visited[new_side].add((n_r, n_c))


    # 탈출까지 최소 소요 시간 리턴
    return min_turn

T = bfs_time_space()



# 시간의 벽을 탈출한 후 미지의 공간의 탈출 경로 탐색 BFS
def bfs_space():

    q = deque()
    visited = set()
    q.append((s_start_r, s_start_c, 0))
    visited.add((s_start_r, s_start_c))

    time = -1

    while q:
        
        #print(q)
        r, c, turn = q.popleft()
        
        # 탈출구에 도착했다면
        if space_maps[r][c] == -1:
            time = turn
            break
            
        for i in range(4):
            nr, nc = r + dx[i], c + dy[i]

            if 0 <= nr < N and 0 <= nc < N and space_maps[nr][nc] != 1 and (nr, nc) not in visited:
                
                num = space_maps[nr][nc]
                
                # 그냥 빈공간이거나 탈출구라면 지나가기
                if num == 0 or num == -1:
                    q.append((nr, nc, turn + 1))
                    visited.add((nr, nc))
                
                # 미래에 이상현상 퍼질 공간이라면, 내가 지나갈 시간보다 퍼지는 시간이 이후라면
                elif T + turn + 1 < num:
                    q.append((nr, nc, turn + 1))
                    visited.add((nr, nc))
        
    return time



# 맵에 이상 현상 시작지점 표시
for s in strange:
    r, c, d, v = s[0], s[1], s[2], s[3]

    space_maps[r][c] = 1

# 맵에 각 이상현상이 확산되는 시간을 표기
for s in strange:
    r, c, d, v = s[0], s[1], s[2], s[3]

    nr, nc = r + dx[d], c + dy[d]
    turn = 1
    # 항상 다음 좌표 생성 시 맵 내부인지 인덱스 체크 꼭 하기!
    while 0 <= nr < N and 0 <= nc < N and space_maps[nr][nc] == 0:
        space_maps[nr][nc] = v * turn
        turn += 1
        nr, nc = nr + dx[d], nc + dy[d]

# 시간의 벽을 탈출할 수 없다면
if T == -1:
    print(-1)

# 아래 시간의 벽 탈출구에 이미 이상현상이 퍼졌다면 실패
elif space_maps[s_start_r][s_start_c] != 0 and T >= space_maps[s_start_r][s_start_c]:
    print(-1)
else:
    time = bfs_space()

    if time == -1:
        print(-1)
    else:
        print(T + time)

0개의 댓글