⚙️ 알고리즘/문제풀이

[Python] 백준 4991 로봇 청소기, 백준 19637 IF문 좀 대신 써줘

dev_zoe 2025. 8. 7. 17:36
반응형

1. 문제 - 백준 4991 로봇 청소기

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

 

💡알고리즘 

 

1️⃣ BFS

로봇에서 각 더러운 칸을 방문할 때, 더러운 칸에서 더러운 칸을 방문할 때 소요되는 최단거리를 계산할때 사용한다.

 

2️⃣ 순열

로봇청소기가 현재 위치에서 어떤 더러운 칸을 먼저 방문할지에 따라서 모든 더러운 칸을 청소하는 데 있어 매번 이동 횟수의 최솟값이 달라지기 때문에, permutations(순열) 을 통해 완전탐색하며 이동 횟수의 최소값을 비교해야한다.

 

✍🏻 막힌, 헷갈린 부분

 

로봇은 같은 칸을 여러 번 방문할 수 있다.

--> visited로 방문처리를 하지 않으면 BFS가 매번 모든 인접한 칸을 그래프 탐색하는거 아닌가?

--> BFS는 로봇청소기가 각 더러운 칸을 방문할 때의 최단 거리를 계산하는 것이므로, 최단 거리를 계산할 때 방문했던 칸을 또 방문한다는 문장에서 이미 최단거리가 아니게 됨. 즉 이때는 원래의 BFS 대로 visited를 통해 방문 여부를 체크하는것이 맞음.

--> 같은 칸을 여러번 방문할 수 있다는 것은, 더러운 칸과 칸 사이를 이동할 때 여러 번 방문할 수 있어 제약이 없다는 의미로 해석

 

더러운 칸을 모두 깨끗한 칸으로 만드는데 필요한 이동 횟수의 최솟값

--> 이부분은 어떻게 구할 수 있을지 아얘 감이 안왔음

--> 어떤 더러운 칸을 먼저 방문할지에 따라서 더러운 칸들을 이동하는 데 소요되는 이동횟수가 달라지므로, 순열을 통해 완전탐색하여 비교한다. (w와 h의 범위가 20이므로 시간제한에 걸리지 않음)

 

✅ 풀이

더러운 칸을 간단하게 먼지라고 하겠습니다 ..!

 

from collections import deque
from itertools import permutations

dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

def bfs(start_x, start_y):
  q = deque([(start_x, start_y)])
  visited[start_x][start_y] = 0

  while q:
    x, y = q.popleft()
    
    for i in range(4):
      nx = x + dx[i]
      ny = y + dy[i]

      if 0<=nx<h and 0<=ny<w and board[nx][ny] != "x" and visited[nx][ny] == -1:
        q.append((nx, ny))
        visited[nx][ny] = visited[x][y] + 1

while True:
  w, h = map(int, input().split())
  if w == 0 and h == 0:
    break

  board = []
  robot_x, robot_y = 0, 0
  dirties = []
  for i in range(h):
    board.append(list(input()))
    for j in range(w):
      if board[i][j] == "o":
        robot_x, robot_y = i, j
      if board[i][j] == "*":
        dirties.append((i, j))

  # 1) 로봇과 각 더러운 칸 사이의 최단 거리 계산
  visited = [[-1] * w for _ in range(h)]
  bfs(robot_x, robot_y)
  robot_to_dust_destinations = [0] * len(dirties)  
  # 이후 첫번째로 방문할 먼지를 순열로 만들기 위해 사용할 로봇과 먼지 사이의 최단거리 배열
  flag = False
  
  for i, (x, y) in enumerate(dirties):
    if visited[x][y] == -1:
      flag = True
      break
    else:         # 로봇-더러운 칸 사이의 최단거리 배열을 최단거리로 갱신
      robot_to_dust_destinations[i] = visited[x][y]
      
  if flag:     # 방문할 수 없는 더러운 칸이 존재하므로 -1 출력 후 종료
    print(-1)
    continue

  # 2) 더러운 칸과 더러운칸 사이의 최단거리 계산
  dist = [[0] * len(dirties) for _ in range(len(dirties))]
  for i in range(len(dirties)):
    visited = [[-1] * w for _ in range(h)]
    bfs(dirties[i][0], dirties[i][1])   # 현재 더러운 칸을 시작점으로 하여 다른 각 칸을 이동하기 위해 소요되는 최단 거리 계산
    
    for j in range(len(dirties)):
      if i == j:
        continue

      if not dist[i][j] or not dist[j][i]:
        dist[i][j] = visited[dirties[j][0]][dirties[j][1]]
        dist[j][i] = dist[i][j]

  # 3) 순열을 통해 방문할 더러운 칸 순서들을 정하여 이동 횟수의 최소값 갱신
  answer = int(1e9)
  for perm in permutations(range(len(dirties))):
    start = perm[0]
    _sum = robot_to_dust_destinations[start]
    for i in range(1, len(perm)):
      end = perm[i]
      _sum += dist[start][end]
      start = end       # start를 그 다음 방문한 더러운 칸으로 바꿔줘야함 (방문한 더러운 칸과 그 다음 방문할 더러운 칸 사이의 거리를 계산하는 것이므로)

    answer = min(answer, _sum)

  print(answer)

 

 

1) 로봇과 각 더러운 칸사이의 최단 거리 계산

  • 어떤 먼지를 먼저 방문하느냐에 따라서 더러운 칸을 모두 깨끗한 칸으로 만드는 데 필요한 이동 횟수의 최소값이 달라진다.
  • 로봇에서 먼저 방문할 먼지 순서는 이후 순열을 통해서 순서를 정한다고 했을 때
    먼저 로봇이 각 먼지를 이동하는 데 소요되는 최단 거리 계산이 필요할 것이다. 
  • BFS를 통해 로봇에서 각 먼지까지 이동하는 데 드는 소요되는 시간을 계산하여 robot_to_dust_destinations 배열에 저장한다.
  • 만약에 이 과정에서 방문할 수 없는 먼지가 있다면 (visited[x][y] == -1) -1을 출력하고 현재 단계의 프로그램을 종료한다.
  # 1) 로봇과 각 먼지사이의 최단 거리 계산
  visited = [[-1] * w for _ in range(h)]   # BFS 이후 로봇에서 각 먼지까지의 최단거리가 적힐 배열
  bfs(robot_x, robot_y)
  robot_to_dust_destinations = [0] * len(dirties)  
  # 이후 첫번째로 방문할 먼지를 순열로 만들기 위해 사용할 로봇과 먼지 사이의 최단거리 배열
  flag = False
  
  for i, (x, y) in enumerate(dirties):
    if visited[x][y] == -1:
      flag = True
      break
    else:         # 로봇-먼지사이의 최단거리 배열을 최단거리로 갱신
      robot_to_dust_destinations[i] = visited[x][y]
      
  if flag:     # 방문할 수 없는 더러운 칸이 존재하므로 -1 출력 후 종료
    print(-1)
    continue

 

 

2) 먼지와 먼지 사이의 최단거리를 계산한다.

  • 로봇에서 먼저 방문할 먼지를 정했다면, 먼지에서 다른 먼지로 이동할 최단 거리를 계산해야할 것이다.
  • 마찬가지로 각 먼지들을 시작점으로 잡고 다른 먼지들과의 최단거리를 BFS를 통해 계산하면 된다.
  • dist[i][j]는 i에서 j까지 방문했을 때의 최단거리를 의미하고, dist[j][i]는 거리이기 때문에 값이 동일하다.
  dist = [[0] * len(dirties) for _ in range(len(dirties))]
  for i in range(len(dirties)):
    visited = [[-1] * w for _ in range(h)]
    bfs(dirties[i][0], dirties[i][1])   # 현재 먼지를 시작점으로 하여 다른 각 먼지를 이동하기 위해 소요되는 최단 거리 계산
    
    for j in range(len(dirties)):
      if i == j:
        continue

      if not dist[i][j] or not dist[j][i]:
        dist[i][j] = visited[dirties[j][0]][dirties[j][1]]
        dist[j][i] = dist[i][j]

 


3) 순열을 통해 방문할 더러운 칸 순서들을 정하고, 완전 탐색을 통해 필요한 이동 횟수의 최소값을 갱신한다.

  • permutations를 통해 방문할 더러운 칸들의 순서를 정한다. 여기서는 더러운 칸에 인덱스로 번호를 붙인다고 했을 때, range(len(dirties))로 하면 [0, 1, 2], [0, 2, 1] ... 등의 결과가 나올것이다.
  • start = perm[0] 은 로봇이 가장 먼저 방문할 먼지 순서이다.
  • _sum 에는 모든 먼지들을 방문할 때 소요되는 이동 횟수의 총합인데, 처음에는 로봇이 첫번째 먼지를 방문하기 위해 소요되는 거리일 것이다. 따라서 _sum = robot_to_dust_destinations[start]
  • 이후 순서에 따라 먼지와 먼지 간의 거리를 더해주고, 총합의 최소값을 갱신한다.
  # 3) 순열을 통해 방문할 먼지 순서들을 정하여 이동 횟수의 최소값 갱신
  answer = int(1e9)
  for perm in permutations(range(len(dirties))):
    start = perm[0]
    _sum = robot_to_dust_destinations[start]
    for i in range(1, len(perm)):
      end = perm[i]
      _sum += dist[start][end]
      start = end       # start를 그 다음 방문한 먼지로 바꿔줘야함 (방문한 먼지와 그 다음 방문할 먼지 사이의 거리를 계산하는 것이므로)

    answer = min(answer, _sum)

  print(answer)

 

2. 문제 - 백준 19637 IF문 좀 대신 써줘

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

 

💡알고리즘 - 이분 탐색

- 입력받는 수마다 범위를 찾아 그에 해당하는 칭호를 찾는 문제이다. 근데 칭호의 갯수가 무려 10^5이고 칭호를 주어야하는 캐릭터의 갯수가 10^5이다..! 그말은 즉슨 O(n^2)로 탐색하는 방법은 시간초과로 통과하지 못할 확률이 높다.

- 칭호가 오름차순으로 정렬되어 있고, 탐색의 시간 복잡도를 줄여야하고, 범위에 해당하는 칭호를 구하는 부분에서 이분탐색임을 알 수 있다.

 

✅ 풀이

import sys
input = sys.stdin.readline

n, m = map(int, input().rstrip().split())
levels = []

for _ in range(n):   # 10^5
  name, limit = input().rstrip().split()
  levels.append((name, int(limit)))

for _ in range(m):
  level = int(input())
  left = 0
  right = n-1
  answer = 0

  while left <= right:
    mid = (left + right) // 2

    if level > levels[mid][1]:
      left = mid + 1
    else:
      right = mid - 1
      answer = mid

  print(levels[answer][0])

 

- 이때 주의할 점은 입력이 시간이 많이 소요되는 편인데, 그냥 Input()으로 입력받으면 칭호의 갯수와 캐릭터의 갯수가 모두 10^5이기 때문에 시간초과가 난다.

- input의 시간을 줄여주는 sys.stdin.readline을 사용해서 입력받을 때의 시간복잡도를 줄이자.

반응형