• 「NOI2019」「LOJ #2720」「Luogu P5471」弹跳


    Description

    平面上有 (n) 个点,分布在 (w imes 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)

    现给出点 (isim n) 的坐标,(m) 开个弹跳装置的信息。对所有的 (iin(1, n]),求 (1) 号点到 (i) 号点的最短耗时。

    Hint

    • (1le nle 7 imes 10^4)
    • (1le mle 1.5 imes 10^5)
    • (1le w, hle n)
    • (1le t_ile 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(nsqrt{n} log n))

    这么做看似完美,但由于 128 MB 的空间限制无法直接 AC,因为边数是 (O(msqrt{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 + msqrt{n})),空间只需要 (O(n+m))

    Code

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : NOI2019 LOJ #2720 Luogu P5471 弹跳
     */
    #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
    ", SSSP::dist[i]);
        return 0;
    }
    
  • 相关阅读:
    c++可调用对象使用示例
    c++ 线程绑定到cpu指定核心
    const在*左边和*右边的区别
    linux c 根据程序名获取进程号
    多线程程序获取进程号和线程号
    虚函数中的默认参数
    ElasticSearch常用查询
    windows10安装windows版Redis
    20220319 14:45:10
    20220320 08:00:06
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/13522885.html
Copyright © 2020-2023  润新知