출제 신호
0-1 BFS는 문제가 "0-1 BFS를 쓰라"고 말해 주지 않습니다. 가중치가 0 아니면
1뿐인 그래프를 스스로 모델링해야 보입니다. 전형적인 신호는 이렇습니다.
- "벽을 최소 몇 개 부수고 도착할 수 있는가" — 빈 칸 이동은 0, 벽 부수기는 1
- "방향을 최소 몇 번 바꾸는가" — 같은 방향 직진은 0, 회전은 1
- "순간이동은 공짜, 걷기는 1초" 류의 두 종류 이동
- "규칙을 최소 몇 번 어기는가", "다리를 최소 몇 개 놓는가"
즉 "비용이 드는 행동의 횟수를 최소화"하고 나머지 이동이 공짜라면,
정점 수가 \(10^6\) 격자급이어도 \(O(V + E)\)로 풉니다. 가중치 종류가 0/1 두
가지라는 점만 확인되면 다익스트라보다 로그 인자 하나가 빠집니다.
풀이 결정 절차
- 이동(전이)을 전부 나열하고 각각의 비용이 0 또는 1로만 떨어지는지 확인합니다.
(0/1이 아니라 0/½ 이상 섞이면 그냥 다익스트라로 갑니다.)
- 상태를 정의합니다 — 격자라면 (행, 열), 회전 문제라면 (행, 열, 방향)처럼
비용 판정에 필요한 정보를 상태에 포함해야 합니다.
- 덱(deque)을 준비합니다 — 비용 0 전이는 앞에, 비용 1 전이는 뒤에 넣습니다.
- 복잡도 검산 — 상태 수 \(\times\) 전이 수가 \(10^7\) 안쪽인지 확인합니다.
핵심 불변식은 "덱 안의 거리값은 항상 단조(차이가 최대 1)"라는 것입니다.
그래서 다익스트라처럼 힙이 없어도 꺼내는 순서가 거리 오름차순이 됩니다.
자주 하는 실수
- 넣을 때 방문 확정 — 가중치 1짜리 BFS 습관대로 enqueue 시점에 방문
처리하면, 나중에 비용 0 경로로 더 싸게 도달할 수 있는 상태가 막힙니다.
거리 비교로 갱신하고, 꺼낼 때 낡은 항목을 버리는 게 안전합니다.
from collections import deque
dist = [[INF] * m for _ in range(n)]
dist[sy][sx] = 0
dq = deque([(sy, sx)])
while dq:
y, x = dq.popleft()
for ny, nx, w in transitions(y, x): # w 는 0 또는 1
if dist[y][x] + w < dist[ny][nx]:
dist[ny][nx] = dist[y][x] + w
if w == 0:
dq.appendleft((ny, nx)) # 0 은 앞으로
else:
dq.append((ny, nx)) # 1 은 뒤로
- 0 전이를 뒤에 넣음 —
appendleft/push_front를 빼먹으면 그냥 틀린
BFS가 됩니다. 0과 1의 행선지(앞/뒤)를 제출 전에 꼭 확인하세요.
- 상태 부족 — "방향 전환 최소화"에서 (행, 열)만 들고 가면 같은 칸을
다른 방향으로 지나는 경우를 구분하지 못해 오답입니다. (행, 열, 방향)으로 확장.
- 다익스트라로 풀어도 되는데 시간이 빠듯한 경우 — \(10^6\) 상태 \(\times\)
\(\log\)가 아슬아슬하면 0-1 BFS로 바꾸는 것 자체가 정해인 문제도 있습니다.
연습 방법
사이드바 연습 목록에서 "벽 부수기" 류의 격자 문제부터 시작해, 상태에
방향이 들어가는 회전 문제로 올라가세요. 문제를 읽을 때 "공짜 행동 / 1짜리
행동"을 표로 적어 보고, 그래프 모델링이 끝난 뒤에야 코드를 시작하는 습관이
중요합니다. 같은 문제를 다익스트라로도 한 번 풀어 두 구현의 차이(힙 vs 덱)를
비교해 보면 이해가 굳어집니다. 태그된 문제 3문제 이상 해결 시 마스터
처리되어 레이팅 CLASS 보너스에 반영됩니다.