4 条题解
-
2
发现机器人在遇到终点或特殊格之前,方向不变,即会在一行 / 一列套圈走。
一旦它进入某个特殊格,就进入了一个状态:
(特殊格,进入方向)
然后它就会沿着这个状态所在的简单环一直走。
所以之后它的路径完全被这个环决定。
因此,判断能不能到终点,就变成:
终点是否在起点到第一个特殊格这段直线上? 或终点是否在这个环的某条“特殊格之间的直线段”上?
所以:
把特殊格和进入方向做成状态,状态图由简单环组成。
查询时枚举四个初始方向,先看 终点能否直达。
否则进入某个环,然后在目标所在行/列二分找相邻特殊格。
反推出可能覆盖目标的边对应的状态,最后在环上算距离取最小转弯次数。

#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 10; const int M = 1e6 + 10; const int INF = 0x3f3f3f3f; // 方向:0=上, 1=右, 2=下, 3=左 const int dx[4] = {-1, 0, 1, 0}; const int dy[4] = {0, 1, 0, -1}; struct node { int pos; // 坐标(行或列) int id; // 特殊格编号 }; int n, m, K, q; int sx[N], sy[N]; // 特殊格坐标 bool isL[N]; // true 表示 L 左转,false 表示 R 右转 // 每行、每列的特殊格,按坐标排序 vector<node> row[M], col[M]; // 状态:(特殊格编号 i, 进入方向 d) // to[i][d] = 状态 (i,d) 的唯一后继状态 pair<int,int> to[N][4]; bool vis[N][4]; int cycleId[N][4]; // 所属环编号 int depth[N][4]; // 在环上的位置 int cycleSize[N << 2]; // 每个环的大小 int cycleCnt = 0; bool cmp(node a, node b) { return a.pos < b.pos; } // 处理棋盘翻折 int wrapX(int x) { if (x < 1) return x + n; if (x > n) return x - n; return x; } int wrapY(int y) { if (y < 1) return y + m; if (y > m) return y - m; return y; } // 从 (x,y) 沿方向 d 出发,找到下一个特殊格的编号 // 如果这一行/列没有特殊格,返回 -1 int nxt(int x, int y, int d) { x = wrapX(x); y = wrapY(y); if (d == 0) { // 上:同一列,找行 < x 的最大特殊格 auto& v = col[y]; if (v.empty()) return -1; // 找第一个 pos >= x auto it = lower_bound(v.begin(), v.end(), x, [](const node& a, int val) { return a.pos < val; }); if (it == v.begin()) return v.back().id; // 没有更小的,翻折到最下面 --it; return it->id; } if (d == 2) { // 下:同一列,找行 > x 的最小特殊格 auto& v = col[y]; if (v.empty()) return -1; // 找第一个 pos > x auto it = upper_bound(v.begin(), v.end(), x, [](int val, const node& a) { return val < a.pos; }); if (it == v.end()) return v[0].id; // 没有更大的,翻折到最上面 return it->id; } if (d == 1) { // 右:同一行,找列 > y 的最小特殊格 auto& v = row[x]; if (v.empty()) return -1; auto it = upper_bound(v.begin(), v.end(), y, [](int val, const node& a) { return val < a.pos; }); if (it == v.end()) return v[0].id; // 翻折到最左边 return it->id; } // d == 3 左:同一行,找列 < y 的最大特殊格 auto& v = row[x]; if (v.empty()) return -1; auto it = lower_bound(v.begin(), v.end(), y, [](const node& a, int val) { return a.pos < val; }); if (it == v.begin()) return v.back().id; // 翻折到最右边 --it; return it->id; } // DFS 找环 void dfs(int u, int d) { vis[u][d] = true; cycleId[u][d] = cycleCnt; cycleSize[cycleCnt]++; auto [v, nd] = to[u][d]; if (v != -1 && !vis[v][nd]) { depth[v][nd] = depth[u][d] + 1; dfs(v, nd); } } // 从状态 a 沿环走到状态 b 的距离 // 如果不在同一个环,返回 INF int dist(pair<int,int> a, pair<int,int> b) { int ca = cycleId[a.first][a.second]; int cb = cycleId[b.first][b.second]; if (ca != cb) return INF; return (depth[b.first][b.second] - depth[a.first][a.second] + cycleSize[ca]) % cycleSize[ca]; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n >> m >> K; for (int i = 1; i <= K; i ++) { // 读入特殊格 char c; cin >> sx[i] >> sy[i] >> c; isL[i] = (c == 'L'); row[sx[i]].push_back({sy[i], i}); col[sy[i]].push_back({sx[i], i}); } // 每行、每列按坐标排序 for (int i = 1; i <= n; i ++) sort(row[i].begin(), row[i].end(), cmp); for (int i = 1; i <= m; i ++) sort(col[i].begin(), col[i].end(), cmp); // 建立状态转移图 for (int i = 1; i <= K; i ++) { for (int d = 0; d < 4; d ++) { // 进入特殊格 i 的方向 int outD = isL[i] ? (d + 3) % 4 : (d + 1) % 4; int j = nxt(sx[i], sy[i], outD); // 从特殊格 i 沿 outD 走,找到下一个特殊格 j to[i][d] = {j, outD}; // 后继状态是 (j, outD) } } // 找所有环 for (int i = 1; i <= K; i++) { for (int d = 0; d < 4; d++) { if (!vis[i][d]) { cycleCnt++; depth[i][d] = 0; dfs(i, d); } } } cin >> q; while (q--) { int xa, ya, xb, yb; cin >> xa >> ya >> xb >> yb; int ans = INF; // p 数组用于记录与起点和终点相关的状态信息。 // p[d][0]:从起点 (xa, ya) 沿方向 d 出发,遇到的第一个特殊格的状态。 // 第一个元素是特殊格编号 u,第二个元素是进入方向 d(沿直线走方向不变)。 // 即状态 (u, d)。如果该方向上没有任何特殊格,则 u = -1。 // 这个状态就是机器人从起点出发、选择初始方向 d 后,进入状态图的入口状态。 // 从起点到该特殊格的直线段上不会发生转弯,所以如果目标在这段直线上,答案为 0(代码后面有特判)。 // // p[d][1]:用于判断目标 (xb, yb) 是否位于某条沿方向 d 的直线段上。 // 计算方式:从目标沿方向 d 的反方向退一格,即 (xb - dx[d], yb - dy[d]), // 再从这个点沿方向 d 出发,找到的下一个特殊格 v。 // 如果目标恰好在从某个特殊格沿方向 d 走出的直线上,那么这条直线的终点特殊格就是 v, // 进入 v 的方向为 d,即状态 (v, d)。如果该方向上没有特殊格,则 v = -1。 // 后续枚举起点的初始方向 d 和目标所在边的方向 nd,通过 dist(p[d][0], p[nd][1]) // 计算从起点进入状态图后,沿环走到目标所在边终点状态所需的最小转弯次数。 // 到达目标所需的转弯次数 = 从初始状态走到 (v, nd) 的步数(每一步对应一次转弯) pair<int,int> p[4][2]; for (int d = 0; d < 4; d++) { // 起点沿方向 d 出发,遇到的第一个特殊格 int u = nxt(xa, ya, d); p[d][0] = {u, d}; // 目标所在方向 d 的边的终点状态 int v = nxt(xb - dx[d], yb - dy[d], d); p[d][1] = {v, d}; } // 枚举:起点方向 d,目标所在边的方向 nd for (int d = 0; d < 4; d ++) { if (p[d][0].first == -1) continue; for (int nd = 0; nd < 4; nd ++) { if (p[nd][1].first == -1) continue; ans = min(ans, dist(p[d][0], p[nd][1])); } } // 特判:起点和终点在同一行/列,且中间没有特殊格 if ((xa == xb && row[xa].empty()) || (ya == yb && col[ya].empty())) { ans = 0; } cout << (ans < INF ? ans : -1) << '\n'; } return 0; }
信息
- ID
- 7512
- 时间
- 2000ms
- 内存
- 612MiB
- 难度
- 8
- 标签
- 递交数
- 18
- 已通过
- 5
- 上传者