D - 迷路と罠マス / Maze and Trap Squares 解説 by admin
GPT 5.4 HighOverview
If we consider each cell as a vertex and up/down/left/right movements as edges, this problem becomes finding the “minimum cost path from S to G.”
Since the cost is \(1\) only when moving to P and \(0\) otherwise, we can solve it efficiently using 0-1 BFS.
Analysis
What we want to minimize in this problem is not the “number of moves” but the number of times we enter P.
For example, if there are:
- A path of 5 steps that steps on
P2 times - A path of 8 steps that steps on
P0 times
the answer is the latter.
Therefore, simply finding the “shortest number of steps” as in a regular BFS will not give the correct answer.
For each move, we define the cost as follows:
- Moving to
P: cost \(1\) - Moving to
S,G,O: cost \(0\) - Cannot move to
B
Then the problem can be rephrased as:
Find the shortest distance from start
Sto goalGin a graph where all edge weights are either \(0\) or \(1\).
Why 0-1 BFS instead of Dijkstra?
Of course, Dijkstra’s algorithm can also solve this.
However, since edge weights in this problem are only of two types, \(0\) and \(1\), using 0-1 BFS leads to a lighter implementation with a time complexity of \(O(V+E)\).
The constraint is \(H \times W \leq 10^6\), which is quite large, so in Python, 0-1 BFS is safer than Dijkstra with \(O(E \log V)\).
How 0-1 BFS works
Regular BFS uses a queue, but 0-1 BFS uses a deque (double-ended queue).
When moving from the current position to an adjacent cell:
- For a cost \(0\) move: use
appendleft - For a cost \(1\) move: use
append
This ensures that the order of extraction from the deque is maintained in “non-decreasing order of distance,” allowing us to find shortest distances just like Dijkstra’s algorithm.
Example
For instance, when moving from a cell to an adjacent cell:
- Moving to
O→ no damage increase → cost \(0\) - Moving to
P→ damage increases by \(1\) → cost \(1\)
So candidates moving to O or G are inserted at the front of the deque, and candidates moving to P are inserted at the back.
This ensures that “paths with less damage” are explored with priority.
Algorithm
- Store the maze as a 1-dimensional array.
- Create a sentinel-padded array by filling the entire border with
B.- This eliminates the need to write boundary checks for each up/down/left/right movement.
- Let
dist[v]be the “minimum damage to reach cellvfromS.” - Set
dist[start] = 0and perform 0-1 BFS. - From the current position
v, examine each adjacent cellnvin the four directions.- If
B, skip - Otherwise: $\( nd = dist[v] + \begin{cases} 1 & (\text{destination is } P) \\ 0 & (\text{otherwise}) \end{cases} \)$
- If
- If
nd < dist[nv], update it.- If
P, add to the back of thedeque - Otherwise, add to the front of the
deque
- If
- When
Gis extracted from the deque, its distance is the minimum, so output the answer. - If
Gis never reached, output-1.
Why this is correct
The cost of each move is determined by “whether the destination is P” and is always \(0\) or \(1\).
Therefore, this problem is a shortest path problem to which 0-1 BFS can be directly applied.
Furthermore, in 0-1 BFS, when a vertex is extracted from the deque, its minimum distance is finalized at that point.
Thus, dist[G] at the moment G is extracted is the desired minimum cumulative damage.
Complexity
- Time complexity: \(O(HW)\)
- Space complexity: \(O(HW)\)
※ Treating each cell as a vertex, the number of vertices is \(O(HW)\), and each vertex has at most 4 edges, so the number of edges is also \(O(HW)\).
Since 0-1 BFS runs in \(O(V+E)\), the overall complexity is \(O(HW)\).
Implementation Details
A sentinel-padded array is used, filling the entire border of the maze with
B.
This eliminates the need for boundary checks like0 <= ni < Hevery time.Instead of a 2-dimensional array, a 1-dimensional array is used, where adjacent cells are represented as:
- Left:
-1 - Right:
+1 - Up:
-PW - Down:
+PW
- Left:
To speed up character comparisons, the board is stored as a
bytearray, andB,P, etc. are compared using their character codes.In 0-1 BFS, the same vertex may be added to the deque multiple times, so a
donearray is used to track “whether a vertex is finalized.” This reduces unnecessary processing.Source Code
import sys
from collections import deque
def main():
input = sys.stdin.buffer.readline
H, W = map(int, input().split())
PW = W + 2
PH = H + 2
N = PW * PH
B = ord('B')
P = ord('P')
grid = bytearray([B]) * N
start = -1
goal = -1
for i in range(H):
row = input().strip()
base = (i + 1) * PW + 1
grid[base:base + W] = row
j = row.find(b'S')
if j != -1:
start = base + j
j = row.find(b'G')
if j != -1:
goal = base + j
INF = 10**18
dist = [INF] * N
done = bytearray(N)
dq = deque([start])
dist[start] = 0
offsets = (-1, 1, -PW, PW)
while dq:
v = dq.popleft()
if done[v]:
continue
done[v] = 1
if v == goal:
print(dist[v])
return
dv = dist[v]
for off in offsets:
nv = v + off
ch = grid[nv]
if ch == B:
continue
nd = dv + (ch == P)
if nd < dist[nv]:
dist[nv] = nd
if ch == P:
dq.append(nv)
else:
dq.appendleft(nv)
print(-1)
if __name__ == "__main__":
main()
This editorial was generated by gpt-5.4-high.
投稿日時:
最終更新: