1. 程式人生 > 實用技巧 >「NOI2019」「LOJ #2720」「Luogu P5471」彈跳

「NOI2019」「LOJ #2720」「Luogu P5471」彈跳

Description

平面上有 \(n\) 個點,分佈在 \(w \times h\) 的網格上。有 \(m\) 個彈跳裝置,由一個六元組描述。第 \(i\) 個裝置有引數:\((p_i, t_i, L_i, R_i, D_i, U_i)\),表示它屬於 \(p_i\) 號點,從點 \(p_i\) 可以通過這個裝置跳到任意一個滿足 \(x\) 座標 \(\in[L_i, R_i]\)\(y\) 座標 \(\in [D_i, U_i]\) 的點,耗時 \(t_i\)

現給出點 \(i\sim n\) 的座標,\(m\) 開個彈跳裝置的資訊。對所有的 \(i\in(1, n]\),求 \(1\)

號點到 \(i\) 號點的最短耗時。

Hint

  • \(1\le n\le 7\times 10^4\)
  • \(1\le m\le 1.5\times 10^5\)
  • \(1\le w, h\le n\)
  • \(1\le t_i\le 10^4\)

Solution

神奇 K-D Tree(二維線段樹)優化建圖題。這裡使用 K-D Tree。因為不會二維線段樹

首先直接暴力連邊會得到一個 \(O(n^2)\)優秀 演算法。考慮優化這個過程,由平面可以聯想到 KDT。

建出 KDT 之後,我們用這顆樹優化我們的建圖過程。對於一個彈跳裝置 \((p, t, L, R, D, U)\),我們從結點 \(p\)

開始,向在規定區域內代表的 KDT 上的結點連一條 \(t\) 長度的邊。最後將 KDT 上的邊的邊權視為 \(0\),圖就建完了。最後 Dijkstra 一波即可。時間複雜度 \(O(n\sqrt{n} \log n)\)

這麼做看似完美,但由於 128 MB 的空間限制無法直接 AC,因為邊數是 \(O(m\sqrt{n})\) 的(KDT 複雜度)。但實質上我們並不需要在建好 KDT 後就大力連邊,KDT只是幫助我們知道一個點可以到達什麼點。而且這個神奇時間複雜度也很難卡過。

我們稱原圖中的點為『實點』,KDT 上的點為『虛點』。為方便起見,設實點 \(x\) 對應的虛點為 \(x+n\)

在跑 Dijkstra 時,我們有如下演算法:

  • 堆頂是實點:
    • 對於其一彈跳裝置 \(y\),限定到達區域為 \(A\),在 KDT 上進行搜尋,設當前虛點為 \(v\),那麼按照 KDT 的套路:
      • 若子樹 \(v \subseteq A\),直接鬆弛 \(v\)
      • 若子樹 \(v \cap A = \varnothing\),跳出;
      • 若區域相交,先鬆弛實點 \(v - n\),然後遞迴。
  • 堆頂是虛點:
    • 鬆弛其對應的實點;
    • 鬆弛其在 KDT 上的左右兒子。

這樣就達到繞開直接建圖的目的。我們可以這樣做的原因是,我們使用資料結構為工具,就已經能做到快速知道某個點可以到達的結點了,那麼大力連邊顯然是冗餘操作。

時間複雜度(STL 二叉堆) \(O((n+m)\log m + m\sqrt{n})\),空間只需要 \(O(n+m)\)

Code

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Problem : NOI2019 LOJ #3159 Luogu P4770 你的名字
 */
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>

using namespace std;
const int N = 7e4 + 5;
const int V = N << 1;
const int K = 2;

int n, m, w, h;
struct area {
    int max[K], min[K];
};
inline bool inside(area x, area y) { // check if y is inside x.
    for (int i = 0; i < K; i++)
        if (y.min[i] < x.min[i] || y.max[i] > x.max[i])
            return false;
    return true;
}
inline bool separated(area x, area y) { // check if x and y is separated.
    for (int i = 0; i < K; i++)
        if (x.min[i] > y.max[i] || x.max[i] < y.min[i])
            return true;
    return false;
}

struct point {
    int dat[K];
    inline int& operator [] (int p) {
        return dat[p];
    }
    inline area toArea() {
        return area{{dat[0], dat[1]}, {dat[0], dat[1]}};
    }
};
pair<point, int> city[N];
int imag[N];

namespace KDT {
    struct Node {
        int lc, rc;
        point p;
        int max[K], min[K];
        int vtxid;
        inline int& operator [] (int d) {
            return p[d];
        }
        inline area limit() {
            return area{{max[0], max[1]}, {min[0], min[1]}};
        }
    } t[N];
    int total = 0;

    #define lc(x) t[x].lc
    #define rc(x) t[x].rc

    inline void pushup(int x) {
        for (int i = 0; i < K; i++) {
            t[x].max[i] = t[x].min[i] = t[x][i];
            if (lc(x)) {
                t[x].max[i] = max(t[x].max[i], t[lc(x)].max[i]);
                t[x].min[i] = min(t[x].min[i], t[lc(x)].min[i]);
            }
            if (rc(x)) {
                t[x].max[i] = max(t[x].max[i], t[rc(x)].max[i]);
                t[x].min[i] = min(t[x].min[i], t[rc(x)].min[i]);
            }
        }
    }

    namespace slctr {
        int dim;
        inline bool comp(pair<point, int>& a, pair<point, int>& b) {
            return a.first[dim] < b.first[dim];
        }
    }
    int build(pair<point, int>* a, int l, int r, int d) {
        if (l > r) return 0;
        int mid = (l + r) >> 1, x = ++total;
        
        slctr::dim = d;
        nth_element(a + l, a + mid + 1, a + r + 1, slctr::comp);
        t[x].p = a[mid].first, imag[t[x].vtxid = a[mid].second] = x;

        lc(x) = build(a, l, mid - 1, d ^ 1);
        rc(x) = build(a, mid + 1, r, d ^ 1);
        return pushup(x), x;
    }
}; // namespace KDT
int root;

struct edge {
    area to;
    int len;
};
vector<edge> G[N];

namespace SSSP {
    struct heapNode {
        int pos, dis;
        inline bool operator < (const heapNode& x) const {
            return dis > x.dis;
        }
    }; priority_queue<heapNode> pq;
    int dist[V]; bool book[V];

    inline void relax(int x, int w) {
        if (dist[x] > w) pq.push(heapNode{x, dist[x] = w});
    }
    void search(int w, area a, int x = root) {
        if (!x) return;
        using namespace KDT;
        area cur = t[x].limit();
        if (separated(a, cur)) return;
        if (inside(a, cur)) return relax(t[x].vtxid + n, w);
        if (inside(a, t[x].p.toArea())) relax(t[x].vtxid, w);
        search(w, a, lc(x)), search(w, a, rc(x));
    }

    void Dijkstra(int src) {
        memset(dist, 0x3f, sizeof(dist));
        memset(book, false, sizeof(book));
        pq.push(heapNode{src, dist[src] = 0});
        while (!pq.empty()) {
            int x = pq.top().pos; pq.pop();
            if (book[x]) continue;
            book[x] = true;
            if (x > n) {
                using namespace KDT;
                relax(x - n, dist[x]);
                if (lc(imag[x - n])) relax(t[lc(imag[x - n])].vtxid + n, dist[x]);
                if (rc(imag[x - n])) relax(t[rc(imag[x - n])].vtxid + n, dist[x]);
            } else {
                for (auto y : G[x])
                    search(y.len + dist[x], y.to);
            }
        }
    }
}

signed main() {
    freopen("jump.in", "r", stdin);
    freopen("jump.out", "w", stdout);

    scanf("%d%d%d%d", &n, &m, &w, &h);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &city[i].first[0], &city[i].first[1]);
        city[i].second = i;
    }
    for (int i = 1; i <= m; i++) {
        area to; int from, len;
        scanf("%d%d", &from, &len);
        scanf("%d%d", &to.min[0], &to.max[0]);
        scanf("%d%d", &to.min[1], &to.max[1]);
        G[from].push_back(edge{to, len});
    }

    root = KDT::build(city, 1, n, 0);
    SSSP::Dijkstra(1);

    for (int i = 2; i <= n; i++)
        printf("%d\n", SSSP::dist[i]);
    return 0;
}