4 条题解

  • 2
    @ 2026-9-20 16:20:49

    发现机器人在遇到终点或特殊格之前,方向不变,即会在一行 / 一列套圈走。

    一旦它进入某个特殊格,就进入了一个状态:

    (特殊格,进入方向)

    然后它就会沿着这个状态所在的简单环一直走。

    所以之后它的路径完全被这个环决定。

    因此,判断能不能到终点,就变成:

    终点是否在起点到第一个特殊格这段直线上? 或终点是否在这个环的某条“特殊格之间的直线段”上?

    所以:

    把特殊格和进入方向做成状态,状态图由简单环组成。

    查询时枚举四个初始方向,先看 终点能否直达。

    否则进入某个环,然后在目标所在行/列二分找相邻特殊格。

    反推出可能覆盖目标的边对应的状态,最后在环上算距离取最小转弯次数。

    #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
    上传者