D - 交互移動ロボット / Alternating Movement Robot Editorial by admin
gpt-6-astra-mediumOverview
We perform Breadth-First Search (BFS) by maintaining the current cell along with the “direction type of the next move” as our state. By representing the alternating vertical and horizontal moves through state transitions, we can find the minimum number of moves.
Analysis
Cells Alone Cannot Distinguish States
For shortest paths in a standard maze, computing the distance to each cell using BFS is sufficient. However, in this problem, even when on the same cell, the reachable next cells differ depending on the previous move’s direction.
For example, if you arrive at a cell from above, you can only move left or right next. On the other hand, if you arrive from the left, you can only move up or down next.
Therefore, a standard BFS that visits each cell only once might prune necessary paths without exploring them, potentially leading to an incorrect answer.
Adding the “Next Movement Axis” to the State
We define the state as follows:
- \((i,j,0)\): At cell \((i,j)\), moving vertically next
- \((i,j,1)\): At cell \((i,j)\), moving horizontally next
With this distinction, the possible moves from each state are uniquely determined:
- If the next move is vertical, move to an adjacent empty cell above or below, and set the next axis to horizontal.
- If the next move is horizontal, move to an adjacent empty cell to the left or right, and set the next axis to vertical.
Revisiting the same cell is treated as a different state if the next movement axis is different. On the other hand, for the same cell and the same axis, the possible future moves are identical, so reaching it once with the shortest distance is sufficient.
Allowing Both for the First Move
Since the first move can be either vertical or horizontal, we initialize both of the following as starting states with distance \(0\):
- \((1,1,0)\)
- \((1,1,1)\)
Since every move has a cost of \(1\), exploring this state graph with BFS yields the shortest distance.
Algorithm
- Initialize an array
diststoring the shortest distance to each state with-1, representing unvisited states. - Set the distance of both starting states (“vertical next” and “horizontal next”) to \(0\), and push them into the queue.
- While the queue is not empty, pop the front state and do the following:
- If the current cell is \((H,W)\), output the distance of that state and terminate.
- Depending on the next movement axis, check the \(2\) adjacent cells (either up/down or left/right).
- If the destination is an empty cell within the grid, consider the state with the flipped axis.
- If that state is unvisited, set its distance to “current distance \(+1\)” and push it into the queue.
- If the queue becomes empty without reaching the goal, output
NONE.
Why This Correctly Finds the Shortest Distance
In the state transitions, we move \(1\) cell along the designated axis and flip the axis each time. Therefore, any path found by the search strictly satisfies the alternating movement condition.
Conversely, any valid robot movement satisfying the condition can be represented as a path on this state graph. Since both initial axes are prepared, no valid path is missed.
BFS processes states in increasing order of distance. Therefore, the distance when a goal state is first popped is minimal among all valid paths.
Complexity
There are \(2\) states per cell, and at most \(2\) transitions are checked from each state. Each state is pushed to the queue at most once.
- Time Complexity: \(O(HW)\)
- Space Complexity: \(O(HW)\)
Implementation Details
Surrounding the Grid with Obstacles
In the provided code, a 1-cell-wide border of obstacles is added around the original grid.
This allows checking whether a move is valid simply by checking blocked[nextCell], without needing boundary checks every time. Since the outer boundary cannot be entered, cells further outside will never be accessed.
Representing Cells and States as Integers
Letting the width including the padding be stride = W + 2, cell \((i,j)\) is represented as:
\(cell = i \times stride + j\)
In this representation, moving up/down corresponds to cell ± stride, and moving left/right corresponds to cell ± 1.
Furthermore, by encoding the state as:
\(state = 2 \times cell + axis\)
the cell index can be retrieved with state / 2, and the next axis with state & 1. Toggling the axis can be done using axis ^ 1.
Marking Visited When Pushing to the Queue
We update dist at the moment the next state is pushed to the queue. This prevents the same state from being added to the queue multiple times.
In the provided code, states are appended to the end of a vector<int>, and an index head is incremented to read them, effectively functioning as a FIFO queue.
Source Code
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int H, W;
cin >> H >> W;
const int stride = W + 2;
const int cells = (H + 2) * stride;
vector<unsigned char> blocked(cells, 1);
for (int i = 1; i <= H; ++i) {
for (int j = 1; j <= W; ++j) {
int a;
cin >> a;
blocked[i * stride + j] = static_cast<unsigned char>(a);
}
}
const int start = stride + 1;
const int goal = H * stride + W;
// State: cell * 2 + next movement axis (0: vertical, 1: horizontal).
vector<int> dist(cells * 2, -1);
vector<int> que;
que.reserve(2 * H * W);
for (int axis = 0; axis < 2; ++axis) {
int state = start * 2 + axis;
dist[state] = 0;
que.push_back(state);
}
for (size_t head = 0; head < que.size(); ++head) {
int state = que[head];
int cell = state / 2;
int axis = state & 1;
if (cell == goal) {
cout << dist[state] << '\n';
return 0;
}
int delta = (axis == 0 ? stride : 1);
for (int nextCell : {cell - delta, cell + delta}) {
if (blocked[nextCell]) continue;
int nextState = nextCell * 2 + (axis ^ 1);
if (dist[nextState] != -1) continue;
dist[nextState] = dist[state] + 1;
que.push_back(nextState);
}
}
cout << "NONE\n";
return 0;
}
This editorial was generated by gpt-6-astra-medium.