• LOJ #2718. 「NOI2018」归程(Dijkstra + Kruskal重构树 + 倍增)


    题意

    给你一个无向图,其中每条边有两个值 (l, a) 代表一条边的长度和海拔。

    其中有 (q) 次询问(强制在线),每次询问给你两个参数 (v, p) ,表示在 (v) 出发,能开车经过海拔 (> p) 的边,其中 (le p) 的边只能步行,步行后不能继续开车了。

    询问它到 (1) 号点最少要步行多远。

    多组数据。(n le 200000~~ m,q le 400000)

    题解

    一个直观的想法,对于每次询问,我们保留 (>p) 的边,然后求出联通块。

    求出它所在联通块到 (1) 距离最小的那个点,就是这次询问的答案。

    (1) 距离的就是把 (1) 当做起点跑一遍单源最短路就行了,注意要用 (Dijkstra)(Spfa) 可以被卡掉。

    复杂度就是 (O((n + m) log n)) 的。

    这下我们只需要询问每个点所在联通块的最小值就行了。

    不难想到,把边按海拔从大到小加入,然后用并查集维护联通块最小值。

    这样的话就可以离线实现这个过程了。

    由于强制在线,我们可以用 可持久化并查集 实现这个过程。但这样其实不好写,常数其实还有一点大。

    我就介绍原题正解的做法,也就是 Kruskal重构树

    (Kruskal) 重构树:

    参考这个讲解。

    考虑求 (Kruskal) 最小生成树的过程,每次我们枚举一条边然后连接两个点,

    我们把这条边变成点,然后边权放到点权上去。我们将连接点所在的子树的根,连到这个点上。

    这样有什么性质呢?

    1. 二叉树
    2. 原树与新树两点间路径上边权(点权)的最大值相等
    3. 子节点的边权小于等于父亲节点(大根堆)
    4. 原树中两点之间路径上边权的最大值等于新树上两点的 (LCA) 的点权。

    这题就是最大生成树,所以是小根堆。

    我们主要是可以用第三条性质(其实很好证明,也可以感性理解),(v) 所在 (> p) 的联通块就是 (v) 在重构树上满足点权 (>p) 最远的祖先所拥有的子树。

    这棵树并不需要显式地建出来,直接隐式地用并查集维护就行了。

    这样的话,我们用倍增预处理,并且在合并时每个节点记下子树的 (min) 值就行了。

    时间复杂度是 (O(n log n)) 的。

    代码

    其实代码也很好写qwq

    #include <bits/stdc++.h>
    
    #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define Cpy(a, b) memcpy(a, b, sizeof(a))
    #define debug(x) cout << #x << ": " << x << endl
    #define DEBUG(...) fprintf(stderr, __VA_ARGS__) 
    
    using namespace std;
    
    inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
    inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
    
    inline int read() {
        int x = 0, fh = 1; char ch = getchar();
        for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
        for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
        return x * fh;
    }
    
    void File() {
        freopen ("return.in", "r", stdin);
        freopen ("return.out", "w", stdout);
    }
    
    int n, m;
    
    const int N = 4e5 + 1e3, M = 8e5 + 1e3;
    
    struct Edge {
        int u, v, a;
    
        inline bool operator < (const Edge &rhs) const {
            return a > rhs.a;
        }
    
    } lt[N];
    
    typedef pair<int, int> PII;
    #define fir first
    #define sec second
    #define mp make_pair
    
    namespace Dijkstra {
    
        int Head[N], Next[M], to[M], val[M], e = 0;
    
        void Init() { Set(Head, 0); e = 0; }
    
        inline void add_edge(int u, int v, int w) { to[++ e] = v; Next[e] = Head[u]; val[e] = w; Head[u] = e; }
    
    	priority_queue<PII, vector<PII>, greater<PII> > P;
        bitset<N> vis; int dis[N];
        void Run() {
    		vis.reset();
            Set(dis, 0x7f); dis[1] = 0; P.push(mp(0, 1));
            while (!P.empty()) {
                PII cur = P.top(); register int u = cur.sec; P.pop();
                if (vis[u]) continue ; vis[u] = true;
    
                for (register int i = Head[u]; i; i = Next[i]) {
                    register int v = to[i];
                    if (chkmin(dis[v], dis[u] + val[i])) 
                        P.push(mp(dis[v], v));
                }
            }
        }
    
    }
    
    namespace Kruskal {
    
        int mina[20][N], mind[N], to[20][N], fa[N], Logn, num = 0;
    
        void Init(int *bas) {
            For (i, 1, n)
                fa[i] = i, mind[i] = bas[i]; num = n;
        }
    
        int find(int x) {
            return x == fa[x] ? x : fa[x] = find(fa[x]); 
        }
    
        inline int Min(int x, int y) {
            return x < y ? x : y;
        }
    
        void Build() {
    
            sort(lt + 1, lt + 1 + m);
    
            For (i, 1, m) {
                int x = lt[i].u, y = lt[i].v, alt = lt[i].a;
    
                int rtx = find(x), rty = find(y);
                if (rtx == rty) continue ;
    
                mind[++ num] = min(mind[rtx], mind[rty]);
    
                fa[num] = 
                    to[0][rtx] = fa[rtx] = 
                    to[0][rty] = fa[rty] = num;
    
                mina[0][rtx] = mina[0][rty] = alt;
            }
    
            Logn = ceil(log(num) / log(2));
            For (j, 1, Logn) For (i, 1, num) {
                to[j][i] = to[j - 1][to[j - 1][i]];
                mina[j][i] = Min(mina[j - 1][i], mina[j - 1][to[j - 1][i]]);
            }
    
        }
    
        inline int Query(int pos, int lim) {
            Fordown (i, Logn, 0)
                if (mina[i][pos] > lim) pos = to[i][pos];
            return mind[pos];
        }
    
    }
    
    int q, k, s;
    
    int main () {
    	File();
    
        int cases = read();
        while (cases --) {
    
            n = read(); m = read();
    
            Dijkstra :: Init();
    
            For (i, 1, m) {
                int u = read(), v = read(), l = read(), a = read();
                lt[i] = (Edge) {u, v, a};
                Dijkstra :: add_edge(u, v, l);
                Dijkstra :: add_edge(v, u, l);
            }
            Dijkstra :: Run();
    
            Kruskal :: Init(Dijkstra :: dis); Kruskal :: Build();
            
            q = read(); k = read(); s = read();
            int ans = 0;
            while (q --) {
                int v = (1ll * read() + k * ans - 1) % n + 1;
                int	p = (1ll * read() + k * ans) % (s + 1);
    
                printf ("%d
    ", ans = Kruskal :: Query(v, p));
            }
    
        }
    
    #ifdef zjp_shadow
    	cerr << (double) clock() / CLOCKS_PER_SEC << endl;
    #endif
    
        return 0;
    }
    
  • 相关阅读:
    man 1 find
    ubuntu20.04输入密码登录不进去
    界面控件DevExpress WinForms皮肤编辑器的这个补丁,你了解了吗?
    UI控件Telerik UI for WinForms新主题——VS2022启发式主题
    干货分享|DevExpress v22.1原版帮助文档下载集合
    Linux下VCS搭建Oracle集群并使用NBU备份到VTL带库真实环境实验
    技术技巧MYSQL常用命令
    sparkSQL实验5.3
    foldByKey和combineByKey
    Scala实验4.1
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/9334916.html
Copyright © 2020-2023  润新知