1 条题解
-
0
题解:机器人网格移动(最小转弯次数问题)
核心结论:将机器人的“位置+方向”视为状态,通过 BFS 预处理特殊格之间的转移关系,查询时结合起点/终点的直线可达性与预处理结果,高效计算最小转弯次数。
一、核心思路(通俗版)
- 问题本质:机器人沿方向直线移动,仅在特殊格(L/R)或到达目标时转弯,求从起点到终点的最少转弯次数。
- 关键观察:
- 直线移动特性:无特殊格时,机器人沿方向无限循环移动(网格翻折),若起点和终点在同一直线上,转弯次数为 0。
- 特殊格的作用:仅特殊格能改变移动方向,转弯次数仅由“经过的特殊格数量”决定(每经过一个特殊格最多转 1 次弯)。
- 状态简化:无需遍历所有网格(n,m 达 1e6),仅关注“特殊格+起点+终点”,因为普通格不影响方向。
- 核心模型:将每个“特殊格+进入方向”作为状态,预处理状态间的转移(直线移动到下一个特殊格的转弯次数),查询时通过 BFS 结合预处理结果求解。
二、关键预处理(O(k log k))
1. 数据结构存储特殊格
- 用哈希表/字典存储特殊格:
grid[(x,y)] = s(s 为 L 或 R),支持 O(1) 查询某个位置是否为特殊格。 - 按行、列分组存储特殊格:
- 行分组:
row[x]存储第 x 行所有特殊格的列坐标(排序)。 - 列分组:
col[y]存储第 y 列所有特殊格的行坐标(排序)。 目的是快速找到“某方向上距离当前位置最近的特殊格”。
- 行分组:
2. 方向定义与转弯规则
- 方向编码:上(0)、右(1)、下(2)、左(3)(顺时针顺序)。
- 转弯规则:
- 左转(L):方向 = (dir - 1) % 4(如右→上)。
- 右转(R):方向 = (dir + 1) % 4(如右→下)。
- 翻折坐标计算:沿方向移动时,超出网格边界后翻折到对侧,公式如下: | 方向 | 移动方式 | 翻折后坐标(超出时) | |------|-------------------------|-------------------------------| | 上 | 行 x 减 1 | x = n - ( (1 - x) % n ) % n | | 下 | 行 x 加 1 | x = (x - 1) % n + 1 | | 左 | 列 y 减 1 | y = m - ( (1 - y) % m ) % m | | 右 | 列 y 加 1 | y = (y - 1) % m + 1 |
3. 预处理特殊格间的转移
- 状态定义:
(x, y, dir)表示机器人在特殊格 (x,y),且即将沿 dir 方向移动(进入特殊格后已完成转弯)。 - 转移计算:对每个状态,沿 dir 方向直线移动,找到下一个特殊格(或检测无限循环):
- 沿 dir 方向遍历,利用行/列分组的排序数组,通过二分查找快速定位最近的特殊格。
- 若移动过程中经过终点(查询时动态判断),则当前转弯次数有效。
- 若到达下一个特殊格 (nx, ny),则新状态为
(nx, ny, new_dir),转弯次数 +1(new_dir 由特殊格类型决定)。
- 预处理结果:存储每个状态到其他状态的最小转弯次数(用 BFS 预处理,因转弯次数是分层的)。
三、查询处理(O(1) 直线判断 + O(BFS 层数) 转移查询)
1. 第一步:判断起点和终点是否相同
若 (a_i, b_i) == (c_i, d_i),输出 0。
2. 第二步:判断直线可达(转弯次数 0)
- 检查起点和终点是否在同一直线上(同行、同列,或沿方向移动时翻折后可达)。
- 沿 4 个方向分别模拟:从起点出发,沿方向直线移动(不考虑特殊格,因起点/终点若为特殊格则失效),若能到达终点,输出 0。
3. 第三步:利用预处理转移求解(转弯次数 ≥1)
- 收集所有可能的“起点→第一个特殊格”的状态:
- 从起点出发,沿 4 个方向移动,找到每个方向上的第一个特殊格 (x, y)。
- 计算到达 (x, y) 时的转弯次数(0,因未经过其他特殊格),状态为
(x, y, new_dir)(new_dir 由 (x,y) 的特殊类型决定)。
- 收集所有可能的“最后一个特殊格→终点”的状态:
- 从终点出发,沿 4 个方向反向移动(即从终点向 dir 方向的反方向移动),找到每个方向上的第一个特殊格 (x, y)。
- 该特殊格的状态
(x, y, dir)能直线到达终点(转弯次数 0)。
- BFS 求解:以“起点→第一个特殊格”的状态为起点,以“能到达终点的特殊格状态”为目标,查询最小转弯次数(预处理的转移次数 + 1?需结合实际转移)。
4. 第四步:处理特殊情况
- 无特殊格(k=0):仅当直线可达时输出 0,否则输出 -1。
- 无法到达:BFS 后未找到目标状态,输出 -1。
四、核心算法优化
1. 直线可达性快速判断
- 同行判断:x1 == x2,且沿左右方向移动时,翻折后能到达 y2。
- 同列判断:y1 == y2,且沿上下方向移动时,翻折后能到达 x2。
- 公式简化:以右方向为例,从 (x, y1) 到 (x, y2) 的条件是 (y2 - y1) ≡ t * m (mod m)(t 为移动步数),且中途无特殊格(或特殊格为起点/终点,失效)。
2. 特殊格转移的二分查找优化
- 沿某方向找下一个特殊格时,利用排序后的行/列数组,通过二分查找快速定位:
- 右方向:在 row[x] 中找大于 y 的最小列坐标。
- 左方向:在 row[x] 中找小于 y 的最大列坐标。
- 上/下方向同理,在 col[y] 中查找。
- 翻折情况处理:若当前方向无特殊格,则翻折后继续查找(等价于在排序数组中循环查找)。
3. BFS 分层处理
- 预处理时,对所有特殊格状态进行 BFS,按转弯次数分层,存储每个状态的最小转弯次数,查询时直接复用。
- 因 k ≤ 1e5,每个特殊格有 4 个方向,总状态数为 4e5,BFS 复杂度 O(4e5),可接受。
五、样例解析(样例 1)
输入
2 2 2,特殊格 (1,1,L)、(2,2,R),查询 (2,1)→(1,2)。
分析
- 起点 (2,1),终点 (1,2),不同行不同列,直线不可达。
- 起点沿“上”方向移动:翻折后到达 (1,1)(特殊格,起点不是特殊格,生效)。
- (1,1) 是 L 格,左转(上→左),转弯次数 +1。
- 沿左方向移动:翻折后到达 (1,2)(终点),总转弯次数 1,输出 1。
六、复杂度分析
- 预处理:O(k log k)(排序行/列特殊格)+ O(4k)(BFS 预处理状态转移)。
- 查询:O(1)(直线判断)+ O(4)(起点到特殊格)+ O(4)(特殊格到终点)+ O(1)(查询预处理结果),总 O(1) 每查询,适配 q ≤ 3e5。
- 空间复杂度:O(k)(存储特殊格和状态转移),可接受。
七、代码框架(核心逻辑)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXK = 1e5 + 5; const int dirs[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; // 上、右、下、左 unordered_map<ll, char> grid; // 存储特殊格,key = x * (1e9+7) + y unordered_map<int, vector<int>> row_spec, col_spec; // 行/列分组的特殊格坐标(排序) int n, m, k, q; // 坐标哈希:(x,y) → 唯一值 ll hash_pos(int x, int y) { return 1LL * x * (1e9 + 7) + y; } // 检查 (x1,y1) 沿 dir 方向是否能直线到达 (x2,y2)(忽略特殊格,或特殊格为起点/终点) bool is_straight(int x1, int y1, int x2, int y2, int dir) { if (x1 == x2 && y1 == y2) return true; int dx = dirs[dir][0], dy = dirs[dir][1]; // 模拟翻折移动,判断是否能到达 // 简化逻辑:同行/同列且翻折后距离一致 if (dx != 0) { // 上下方向(同列) if (y1 != y2) return false; int len = abs(x2 - x1); return len % n == 0; // 翻折后可达 } else { // 左右方向(同行) if (x1 != x2) return false; int len = abs(y2 - y1); return len % m == 0; } } // 从 (x,y) 沿 dir 方向找下一个特殊格,返回 (nx, ny, 距离是否合法) tuple<int, int, bool> find_next_spec(int x, int y, int dir) { int dx = dirs[dir][0], dy = dirs[dir][1]; if (dx != 0) { // 上下方向(列 y 固定) auto it = col_spec.find(y); if (it == col_spec.end()) return {-1, -1, false}; auto &cols = it->second; if (dx == -1) { // 上:找小于 x 的最大行 auto pos = upper_bound(cols.begin(), cols.end(), x); if (pos != cols.begin()) { pos--; return {*pos, y, true}; } else { // 翻折,找最大行 return {cols.back(), y, true}; } } else { // 下:找大于 x 的最小行 auto pos = lower_bound(cols.begin(), cols.end(), x); if (pos != cols.end()) { return {*pos, y, true}; } else { // 翻折,找最小行 return {cols[0], y, true}; } } } else { // 左右方向(行 x 固定) auto it = row_spec.find(x); if (it == row_spec.end()) return {-1, -1, false}; auto &rows = it->second; if (dy == 1) { // 右:找大于 y 的最小列 auto pos = lower_bound(rows.begin(), rows.end(), y); if (pos != rows.end()) { return {x, *pos, true}; } else { // 翻折,找最小列 return {x, rows[0], true}; } } else { // 左:找小于 y 的最大列 auto pos = upper_bound(rows.begin(), rows.end(), y); if (pos != rows.begin()) { pos--; return {x, *pos, true}; } else { // 翻折,找最大列 return {x, rows.back(), true}; } } } } // 预处理特殊格间的转移:dist[(x,y,dir)] = 最小转弯次数 unordered_map<ll, int> dist; void preprocess() { queue<tuple<int, int, int>> q; // 初始化所有特殊格的 4 个方向状态 for (auto &[pos, s] : grid) { int x = pos / (1e9 + 7), y = pos % (1e9 + 7); for (int dir = 0; dir < 4; dir++) { ll state = hash_pos(x, y) * 4 + dir; dist[state] = 0; q.emplace(x, y, dir); } } // BFS 预处理转移 while (!q.empty()) { auto [x, y, dir] = q.front(); q.pop(); ll curr_state = hash_pos(x, y) * 4 + dir; // 沿 dir 方向找下一个特殊格 auto [nx, ny, ok] = find_next_spec(x, y, dir); if (!ok) continue; // 计算下一个方向(根据特殊格类型) char s = grid[hash_pos(nx, ny)]; int new_dir; if (s == 'L') new_dir = (dir - 1 + 4) % 4; else new_dir = (dir + 1) % 4; // 新状态 ll new_state = hash_pos(nx, ny) * 4 + new_dir; if (dist.count(new_state) == 0 || dist[new_state] > dist[curr_state] + 1) { dist[new_state] = dist[curr_state] + 1; q.emplace(nx, ny, new_dir); } } } int solve_query(int a, int b, int c, int d) { // 情况 1:起点 == 终点 if (a == c && b == d) return 0; // 情况 2:直线可达(转弯次数 0) for (int dir = 0; dir < 4; dir++) { if (is_straight(a, b, c, d, dir)) { return 0; } } // 情况 3:无特殊格,无法到达 if (k == 0) return -1; // 情况 4:通过特殊格转移 int min_turn = INT_MAX; // 收集起点→第一个特殊格的状态(转弯次数 0) vector<tuple<int, int, int>> start_states; for (int dir = 0; dir < 4; dir++) { auto [nx, ny, ok] = find_next_spec(a, b, dir); if (!ok) continue; // 检查从起点到 (nx, ny) 的路径是否经过终点(若经过则转弯次数 0,已在情况 2 处理) char s = grid[hash_pos(nx, ny)]; int new_dir = (s == 'L') ? (dir - 1 + 4) % 4 : (dir + 1) % 4; start_states.emplace_back(nx, ny, new_dir); } // 收集能到达终点的特殊格状态(转弯次数 0) unordered_set<ll> end_states; for (int dir = 0; dir < 4; dir++) { // 反向方向:从终点向 dir 方向移动,等价于从特殊格向 dir 方向移动到终点 int rev_dir = (dir + 2) % 4; auto [nx, ny, ok] = find_next_spec(c, d, rev_dir); if (!ok) continue; ll state = hash_pos(nx, ny) * 4 + dir; end_states.insert(state); } // 计算最小转弯次数 for (auto [x, y, dir] : start_states) { ll state = hash_pos(x, y) * 4 + dir; if (end_states.count(state)) { min_turn = min(min_turn, 1); // 起点→特殊格(0 转)+ 特殊格→终点(0 转),总 1 转
- 1
信息
- ID
- 4751
- 时间
- 2000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 5
- 已通过
- 1
- 上传者