← 返回题目页

T0459 走迷宫

标程

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

// 「下一个还没被回答的下标」的并查集:回答过的出口直接从链上摘掉
struct NextDSU {
    vector<int> p;
    explicit NextDSU(int n) : p(n + 1) { iota(p.begin(), p.end(), 0); }
    int find(int x) {
        while (p[x] != x) {
            p[x] = p[p[x]];
            x = p[x];
        }
        return x;
    }
    void erase(int x) { p[x] = find(x + 1); }
};

struct State {
    ll x, y;
    int d;
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    ll sx, sy;
    cin >> n >> m >> sx >> sy;

    vector<pair<ll, ll>> obs(n);
    for (auto &o : obs) cin >> o.first >> o.second;
    vector<pair<ll, ll>> ex(m);
    for (auto &e : ex) cin >> e.first >> e.second;

    // 每一行的障碍 x(递增)、每一列的障碍 y(递增)
    map<ll, vector<ll>> rowObs, colObs;
    for (auto &o : obs) {
        rowObs[o.second].push_back(o.first);
        colObs[o.first].push_back(o.second);
    }
    for (auto &kv : rowObs) sort(kv.second.begin(), kv.second.end());
    for (auto &kv : colObs) sort(kv.second.begin(), kv.second.end());

    // 出口按 (y,x) 和 (x,y) 各排一份,分别用于横向和纵向的松弛
    vector<int> ordH(m), ordV(m);
    iota(ordH.begin(), ordH.end(), 0);
    iota(ordV.begin(), ordV.end(), 0);
    sort(ordH.begin(), ordH.end(), [&](int i, int j) {
        return ex[i].second != ex[j].second ? ex[i].second < ex[j].second : ex[i].first < ex[j].first;
    });
    sort(ordV.begin(), ordV.end(), [&](int i, int j) {
        return ex[i].first != ex[j].first ? ex[i].first < ex[j].first : ex[i].second < ex[j].second;
    });
    NextDSU dsuH(m), dsuV(m);
    vector<ll> answer(m, -1);
    // 出口正好就在起点:一步都不用走
    for (int i = 0; i < m; i++) {
        if (ex[i].first == sx && ex[i].second == sy) answer[i] = 0;
    }

    // 在 ordH 中找到第一个 (行, 列) >= (ry, rx) 的位置
    auto lowerH = [&](ll ry, ll rx) {
        int lo = 0, hi = m;
        while (lo < hi) {
            int mid = (lo + hi) / 2;
            ll my = ex[ordH[mid]].second, mx = ex[ordH[mid]].first;
            if (my < ry || (my == ry && mx < rx)) lo = mid + 1;
            else hi = mid;
        }
        return lo;
    };
    auto lowerV = [&](ll rx, ll ry) {
        int lo = 0, hi = m;
        while (lo < hi) {
            int mid = (lo + hi) / 2;
            ll mx = ex[ordV[mid]].first, my = ex[ordV[mid]].second;
            if (mx < rx || (mx == rx && my < ry)) lo = mid + 1;
            else hi = mid;
        }
        return lo;
    };

    // 把这一趟滑行经过的出口答案更新为 cost(BFS 保证第一次赋值就是最小值)
    auto relaxH = [&](ll ry, ll lo, ll hi, ll cost) {
        int i = dsuH.find(lowerH(ry, lo));
        while (i < m) {
            ll my = ex[ordH[i]].second, mx = ex[ordH[i]].first;
            if (my != ry || mx > hi) break;
            // BFS 保证第一次赋值就是最小步数;已答过的出口只从链上摘掉
            if (answer[ordH[i]] == -1) answer[ordH[i]] = cost;
            dsuH.erase(i);
            i = dsuH.find(i);
        }
    };
    auto relaxV = [&](ll rx, ll lo, ll hi, ll cost) {
        int i = dsuV.find(lowerV(rx, lo));
        while (i < m) {
            ll mx = ex[ordV[i]].first, my = ex[ordV[i]].second;
            if (mx != rx || my > hi) break;
            // BFS 保证第一次赋值就是最小步数;已答过的出口只从链上摘掉
            if (answer[ordV[i]] == -1) answer[ordV[i]] = cost;
            dsuV.erase(i);
            i = dsuV.find(i);
        }
    };

    auto keyOf = [](ll x, ll y, int d) -> unsigned long long {
        unsigned long long h = 1469598103934665603ULL;
        auto mix = [&](unsigned long long v) {
            h ^= v;
            h *= 1099511628211ULL;
        };
        mix((unsigned long long)x);
        mix((unsigned long long)y);
        mix((unsigned long long)d);
        return h;
    };

    const ll INF = (ll)4e18;
    unordered_map<unsigned long long, int> dist;
    queue<State> q;
    for (int d = 0; d < 4; d++) {
        dist[keyOf(sx, sy, d)] = 0;
        q.push({sx, sy, d});
    }

    while (!q.empty()) {
        State cur = q.front();
        q.pop();
        int steps = dist[keyOf(cur.x, cur.y, cur.d)];
        ll x = cur.x, y = cur.y;
        int d = cur.d;

        ll lx = x, ly = y;      // 落点
        ll lo = 0, hi = 0;      // 这一趟经过的区间(含出发点)
        bool blocked = false;   // 方向上有没有障碍

        if (d == 0 || d == 2) {
            // 横向滑行:找这一行里最近的障碍
            ll left = -INF, right = INF;
            auto it = rowObs.find(y);
            if (it != rowObs.end()) {
                const vector<ll> &v = it->second;
                // 第一个 >= x 的障碍:它在 x 的右边;它的前一个在 x 的左边
                auto p = lower_bound(v.begin(), v.end(), x);
                if (p != v.end()) right = *p;
                if (p != v.begin()) left = *(p - 1);
            }
            if (d == 0) {
                lo = x;
                hi = (right == INF) ? INF : right - 1;
                if (right != INF) {
                    blocked = true;
                    lx = right - 1;
                }
            } else {
                lo = (left == -INF) ? -INF : left + 1;
                hi = x;
                if (left != -INF) {
                    blocked = true;
                    lx = left + 1;
                }
            }
            relaxH(y, lo, hi, steps + 1);
        } else {
            // 纵向滑行:找这一列里最近的障碍
            ll down = -INF, up = INF;
            auto it = colObs.find(x);
            if (it != colObs.end()) {
                const vector<ll> &v = it->second;
                // 第一个 >= y 的障碍:它在 y 的上边;它的前一个在 y 的下边
                auto p = lower_bound(v.begin(), v.end(), y);
                if (p != v.end()) up = *p;
                if (p != v.begin()) down = *(p - 1);
            }
            if (d == 1) {
                lo = y;
                hi = (up == INF) ? INF : up - 1;
                if (up != INF) {
                    blocked = true;
                    ly = up - 1;
                }
            } else {
                lo = (down == -INF) ? -INF : down + 1;
                hi = y;
                if (down != -INF) {
                    blocked = true;
                    ly = down + 1;
                }
            }
            relaxV(x, lo, hi, steps + 1);
        }

        if (!blocked) continue;   // 这个方向没有障碍:滑向无穷远,不会再有下一站

        // 滑完之后只能左转或右转 90 度
        for (int nd : {(d + 1) % 4, (d + 3) % 4}) {
            unsigned long long key = keyOf(lx, ly, nd);
            if (dist.find(key) != dist.end()) continue;
            dist[key] = steps + 1;
            q.push({lx, ly, nd});
        }
    }

    for (ll v : answer) cout << v << "\n";
    return 0;
}

动画演示