1. 程式人生 > >LOJ #2719. 「NOI2018」歸程(Dijsktra + Kruskal重構樹 + 倍增)

LOJ #2719. 「NOI2018」歸程(Dijsktra + Kruskal重構樹 + 倍增)

繼續 get lock empty name 相等 noi using bitset

題意

給你一個無向圖,其中每條邊有兩個值 \(l, a\) 代表一條邊的長度和海拔。

其中有 \(q\) 次詢問(強制在線),每次詢問給你兩個參數 \(v, p\) ,表示在 \(v\) 出發,能開車經過海拔 \(> p\) 的邊,其中 \(\le p\) 的邊只能步行,步行後不能繼續開車了。

詢問它到 \(1\) 號點最少要步行多遠。

多組數據。\(n \le 200000~~ m,q \le 400000\)

題解

一個直觀的想法,對於每次詢問,我們保留 \(>p\) 的邊,然後求出聯通塊。

求出它所在聯通塊到 \(1\) 距離最小的那個點,就是這次詢問的答案。

\(1\) 距離的就是把 \(1\)

當做起點跑一遍單源最短路就行了,註意要用 \(Dijsktra\)\(Spfa\) 可以被卡掉。

復雜度就是 \(O((n + m) \log n)\) 的。

這下我們只需要詢問每個點所在聯通塊的最小值就行了。

不難想到,把邊按海拔從大到小加入,然後用並查集維護聯通塊最小值。

這樣的話就可以離線實現這個過程了。

由於強制在線,我們可以用 可持久化並查集 實現這個過程。但這樣其實不好寫,常數其實還有一點大。

我就介紹原題正解的做法,也就是 Kruskal重構樹

\(Kruskal\) 重構樹:

參考這個講解。

考慮求 \(Kruskal\) 最小生成樹的過程,每次我們枚舉一條邊然後連接兩個點,

我們把這條邊變成點,然後邊權放到點權上去。我們將連接點所在的子樹的根,連到這個點上。

這樣有什麽性質呢?

  1. 二叉樹
  2. 原樹與新樹兩點間路徑上邊權(點權)的最大值相等
  3. 子節點的邊權小於等於父親節點(大根堆)
  4. 原樹中兩點之間路徑上邊權的最大值等於新樹上兩點的 \(LCA\) 的點權。

這題就是最大生成樹,所以是小根堆。

我們主要是可以用第三條性質(其實很好證明,也可以感性理解),\(v\) 所在 \(> p\) 的聯通塊就是 \(v\) 在重構樹上滿足點權 \(>p\) 最遠的祖先所擁有的子樹。

這樣的話,我們用倍增預處理,然後每個節點記下子樹的 \(min\) 值就行了。

代碼也很好寫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() {
#ifdef zjp_shadow
    freopen ("return.in", "r", stdin);
    freopen ("return.out", "w", stdout);
#endif
}

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 Dijsktra {

    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();

        Dijsktra :: Init();

        For (i, 1, m) {
            int u = read(), v = read(), l = read(), a = read();
            lt[i] = (Edge) {u, v, a};
            Dijsktra :: add_edge(u, v, l);
            Dijsktra :: add_edge(v, u, l);
        }
        Dijsktra :: Run();

        Kruskal :: Init(Dijsktra :: 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\n", ans = Kruskal :: Query(v, p));
        }

    }

#ifdef zjp_shadow
    cerr << (double) clock() / CLOCKS_PER_SEC << endl;
#endif

    return 0;
}

LOJ #2719. 「NOI2018」歸程(Dijsktra + Kruskal重構樹 + 倍增)