D - 迷路と罠マス / Maze and Trap Squares 解説 by admin
gemini-3-flash-thinkingOverview
This is a shortest path problem on a maze consisting of an \(H \times W\) grid, where we need to find the minimum damage from the start position S to the goal G. The cost of passing through a normal cell is \(0\), and the cost of passing through a trap cell P is \(1\).
Analysis
The key point of this problem is that we need to minimize the “cumulative damage” rather than the “number of moves (steps)”.
Standard breadth-first search (BFS) is suitable for finding shortest distances when all movement costs are equal (e.g., all \(1\)). However, in this problem, costs differ depending on the type of the destination cell: \(0\) (for S, G, O) or \(1\) (for P).
For shortest path problems on graphs where costs are either \(0\) or \(1\), we can use a technique called 0-1 BFS, which runs in \(O(V + E)\) — faster than the standard Dijkstra’s algorithm at \(O(E \log V)\).
Algorithm
0-1 BFS
We use a double-ended queue (deque) to explore in order of increasing cumulative damage.
- Initialize the damage at the start position
Sto \(0\), and all other cells to infinity (\(\infty\)). - Add
Sto thedeque. - Repeat the following operations until the
dequeis empty:- Pop the current cell \(u\) from the front of the
deque. - If the minimum damage for this cell has already been finalized (already visited), skip it.
- For each adjacent cell \(v\) in 4 directions:
- If \(v\) is not a wall
Band the new damage to \(v\) is less than the currently recorded value:- If the destination is a trap cell
P(cost 1): Add \(+1\) to the damage and push \(v\) to the back of thedeque. - If the destination is any other cell (cost 0):
Keep the damage as is (\(+0\)) and push \(v\) to the front of the
deque.
- If the destination is a trap cell
- If \(v\) is not a wall
- Pop the current cell \(u\) from the front of the
By prioritizing the exploration of cost \(0\) moves (adding them to the front of the queue), we always explore paths in order of minimum damage first.
Complexity
Let the total number of cells be \(N = H \times W\).
- Time Complexity: \(O(H \times W)\) Each cell is visited at most once, and for each cell we check 4 directions, so the algorithm runs in linear time with respect to the grid size.
- Space Complexity: \(O(H \times W)\) \(O(H \times W)\) memory is used for storing the grid information and the array recording the minimum damage for each cell.
Implementation Notes
Grid flattening: In Python, handling the grid as a 1-dimensional string or list can be faster than using a 2-dimensional array (list of lists). In this code, we flatten it with
grid = "".join(input_data[2:]).Efficient I/O: Since the input size can be as large as \(H \times W \leq 10^6\), we use
sys.stdin.read().split()to read all input at once, reducing execution time.Visited management: In 0-1 BFS, the same cell may be added to the
dequemultiple times. Therefore, it is efficient to check whether a cell has already been finalized using aprocessedflag when popping it from the deque.Source Code
import sys
from collections import deque
def solve():
# Read all input at once and split by whitespace
input_data = sys.stdin.read().split()
if not input_data:
return
# H: number of rows, W: number of columns
H = int(input_data[0])
W = int(input_data[1])
# Concatenate all rows into a single string for faster indexing
# Since H * W <= 10^6, this is efficient in Python.
grid = "".join(input_data[2:])
# Find the indices of start 'S' and goal 'G' in the flattened grid
start_idx = grid.find('S')
goal_idx = grid.find('G')
# Distance array initialized with a value larger than any possible damage
# H * W is a safe upper bound for cumulative damage.
INF = H * W + 1
dist = [INF] * (H * W)
dist[start_idx] = 0
# Array to track whether a cell's neighbors have already been processed
# In 0-1 BFS, the first time a node is popped, its distance is minimal.
processed = [False] * (H * W)
# Deque for 0-1 BFS
dq = deque([start_idx])
popleft = dq.popleft
appendleft = dq.appendleft
append = dq.append
while dq:
u = popleft()
# If this cell has already been processed, skip it
if processed[u]:
continue
processed[u] = True
# Current cumulative damage to reach cell u
d = dist[u]
# If we reached the goal, output the minimal damage and exit
if u == goal_idx:
print(d)
return
# Determine current row and column for boundary checks
r, c = divmod(u, W)
# Explore neighbors in four directions: Up, Down, Left, Right
# Up
if r > 0:
v = u - W
char_v = grid[v]
if char_v != 'B':
if char_v == 'P':
# Cost 1 for entering a trapマス 'P'
if dist[v] > d + 1:
dist[v] = d + 1
append(v)
else:
# Cost 0 for entering 'S', 'G', or 'O'
if dist[v] > d:
dist[v] = d
appendleft(v)
# Down
if r < H - 1:
v = u + W
char_v = grid[v]
if char_v != 'B':
if char_v == 'P':
if dist[v] > d + 1:
dist[v] = d + 1
append(v)
else:
if dist[v] > d:
dist[v] = d
appendleft(v)
# Left
if c > 0:
v = u - 1
char_v = grid[v]
if char_v != 'B':
if char_v == 'P':
if dist[v] > d + 1:
dist[v] = d + 1
append(v)
else:
if dist[v] > d:
dist[v] = d
appendleft(v)
# Right
if c < W - 1:
v = u + 1
char_v = grid[v]
if char_v != 'B':
if char_v == 'P':
if dist[v] > d + 1:
dist[v] = d + 1
append(v)
else:
if dist[v] > d:
dist[v] = d
appendleft(v)
# If the goal 'G' is not reachable from 'S'
print("-1")
if __name__ == '__main__':
solve()
This editorial was generated by gemini-3-flash-thinking.
投稿日時:
最終更新: