[BOJ] Q4991 로봇 청소기

Question

Language: Python

Difficulty: Gold 1

특정 출발지에서 로봇 청소기를 출발시켜서 더러운 지점들을 모두 청소하기 위해 이동해야하는 최소 이동거리를 구하는 문제로, 더러운 지점의 갯수가 최대 10개이기 때문에 Bruteforce을 활용하여 모든 경우를 고려하여 이동하는 최소 거리를 구할 수 있다.

이때, 각 좌표간에 거리를 구해야하기 때문에 해당 과정에서 bfs을 활용한다.

Solution 1

from collections import deque
from itertools import permutations
from math import inf
import sys

def find_distances(length,areas):
    distances=[[inf] * length for _ in range(length)]

    dy=[-1,0,1,0]
    dx=[0,1,0,-1]
    for i in range(length):
        start_row,start_col=areas[i]
        queue=deque([(start_row,start_col,0)])
        visited=[[inf] * n_cols for _ in range(n_rows)]

        #각 점에 대해 bfs을 수행해서 각 좌표까지 도달 가능한 거리 탐색
        while queue:
            row,col,distance=queue.popleft()
            #저장되어 있는 최소 거리보다 크면 해당 점을 탐색할 필요가 없으니 다음 경우 조사
            if distance > visited[row][col]:
                continue

            visited[row][col]=distance

            for dir in range(4):
                next_row=row+dy[dir]
                next_col=col+dx[dir]

                #격자 범위를 벗어나는 경우
                if next_row < 0 or next_row >=n_rows or next_col < 0 or next_col>=n_cols:
                    continue
                #벽이 있는 경우
                if board[next_row][next_col] == "x":
                    continue

                if (distance + 1) < visited[next_row][next_col]:
                    visited[next_row][next_col]=distance+1
                    queue.append((next_row,next_col,distance+1))
        
        #각 위치 좌표에 도달하는 거리 초기화
        for j in range(i+1,length):
            next_row,next_col=areas[j]
            distances[i][j]=visited[next_row][next_col]
            distances[j][i]=visited[next_row][next_col]

    return distances


def solution():
    areas=[]
    start_row,start_col=0,0
    
    #지나가야하는 좌표 구하기
    for row in range(n_rows):
        for col in range(n_cols):
            if board[row][col] == "o":
                start_row,start_col=row,col
            
            if board[row][col] == "*":
                areas.append((row,col))
    
    #초기 좌표도 포함시킨다.
    areas.insert(0,(start_row,start_col))
    length=len(areas)
    distances=find_distances(length,areas)
    shortest_distance=inf
    
    #더러운 좌표에 대한 순열을 구해서 좌표 순서에 따른 총 이동 거리를 구한다.
    for permutation in permutations(range(1,length)):
        distance=0
        previous_index=0
        #시작 좌표를 기점으로 더러운 좌표의 순열을 탐색한다.
        for next_index in permutation:
            distance+=distances[previous_index][next_index]
            previous_index=next_index
        
        shortest_distance=min(shortest_distance,distance)
    
    if shortest_distance == inf:
        print(-1)
    else:
        print(shortest_distance)
        
if __name__ == "__main__":

    while True:
        n_cols,n_rows=map(int,input().split())

        #입력의 끝
        if (n_rows,n_cols)==(0,0):
            break

        board=[list(input()) for _ in range(n_rows)]
        solution()

위와 같이 거쳐야하는 좌표의 갯수가 적은 경우에는 모든 좌표를 고려하는 Bruteforce 방식을 취할 수 있지만, node의 갯수가 많아지면 불가능해진다. 이때는 TSP(Traveling Salesman Problem) 알고리즘을 이용해서 문제를 풀이한다.

from collections import deque
from math import inf
import sys

def find_distances(length,areas):
    distances=[[inf] * length for _ in range(length)]

    dy=[-1,0,1,0]
    dx=[0,1,0,-1]
    for i in range(length):
        start_row,start_col=areas[i]
        queue=deque([(start_row,start_col,0)])
        visited=[[inf] * n_cols for _ in range(n_rows)]

        #각 점에 대해 bfs을 수행해서 각 좌표까지 도달 가능한 거리 탐색
        while queue:
            row,col,distance=queue.popleft()
            #저장되어 있는 최소 거리보다 크면 해당 점을 탐색할 필요가 없으니 다음 경우 조사
            if distance > visited[row][col]:
                continue

            visited[row][col]=distance

            for dir in range(4):
                next_row=row+dy[dir]
                next_col=col+dx[dir]

                #격자 범위를 벗어나는 경우
                if next_row < 0 or next_row >=n_rows or next_col < 0 or next_col>=n_cols:
                    continue
                #벽이 있는 경우
                if board[next_row][next_col] == "x":
                    continue

                if (distance + 1) < visited[next_row][next_col]:
                    visited[next_row][next_col]=distance+1
                    queue.append((next_row,next_col,distance+1))
        
        #각 위치 좌표에 도달하는 거리 초기화
        for j in range(i+1,length):
            next_row,next_col=areas[j]
            distances[i][j]=visited[next_row][next_col]
            distances[j][i]=visited[next_row][next_col]

    return distances

#TSP을 적용을 위한 dfs
def dfs(n,last,visited):
    #모든 지점을 다 순환한 경우
    if visited == (1<<n)-1:
        return 0
    
    #이미 이전에 처리된 경우이면 중간값 반환
    if dp[last][visited] != -1:
        return dp[last][visited]
    
    distance=inf
    for next_location in range(1,n):
        #이미 방문한 경우
        if visited & 1<<(next_location) == 1<<(next_location):
            continue

        #도달할 수 없는 경우
        if distances[last][next_location] == inf:
            continue
        
        distance=min(distance,dfs(n,next_location,visited  | 1<<(next_location)) + distances[last][next_location])
    
    dp[last][visited]=distance
    return distance


def solution():
    global dp,distances
    areas=[]
    start_row,start_col=0,0
    
    #지나가야하는 좌표 구하기
    for row in range(n_rows):
        for col in range(n_cols):
            if board[row][col] == "o":
                start_row,start_col=row,col
            
            if board[row][col] == "*":
                areas.append((row,col))
    
    #초기 좌표도 포함시킨다.
    areas.insert(0,(start_row,start_col))
    length=len(areas)
    distances=find_distances(length,areas)

    dp=[[-1] * (1<<length) for _ in range(length)]

    shortest_distance=dfs(length,0,1)

    
    if shortest_distance == inf:
        print(-1)
    else:
        print(shortest_distance)
if __name__ == "__main__":
    while True:
        n_cols,n_rows=map(int,input().split())

        #입력의 끝
        if (n_rows,n_cols)==(0,0):
            break

        board=[list(input()) for _ in range(n_rows)]
        solution()

댓글남기기