1. 程式人生 > >Educational Codeforces Round 33 (Rated for Div. 2) F. Subtree Minimum Query(主席樹合併)

Educational Codeforces Round 33 (Rated for Div. 2) F. Subtree Minimum Query(主席樹合併)

題意

給定一棵 \(n\) 個點的帶點權樹,以 \(1\) 為根, \(m\) 次詢問,每次詢問給出兩個值 \(p, k\) ,求以下值:

\(p\) 的子樹中距離 \(p \le k\) 的所有點權最小值,詢問強制線上。

\(n \le 10^5 , m \le 10^6, TL = 6s\)

題解

如果不強制線上,直接線段樹合併就做完了。

強制線上,不難想到用一些可持久化的結構來維護這些東西。

其實可以類似線段樹合併那樣考慮,也就是說每次合併的時候我們依然使用兒子的資訊。

只要在合併 \(x, y\) 共有部分的時候建出新節點,然後權值是 \(x, y\) 權值的較小值,其他的部分直接連向那些單獨有的子樹資訊即可。

其實實現是這樣的:

    int Merge(int x, int y) {
        if (!x || !y) return x | y;
        int o = Newnode();
        ls[o] = Merge(ls[x], ls[y]);
        rs[o] = Merge(rs[x], rs[y]);
        minv[o] = min(minv[x], minv[y]);
        return o;
    }

這樣的話,既可以保留子樹資訊,又可以得到這個節點新的資訊。

最後空間複雜度就是 \(O(n \log n)\) ,時間複雜度是 \(O((n + m) \log n)\)

的。

總結

強制線上問題,用可持久化資料結構去解決就行了,也就是把平常的資料結構記歷史版本,並儘量用之前的資訊。

程式碼

#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__)
#define pb push_back

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;}
template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;}

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("F.in", "r", stdin);
    freopen ("F.out", "w", stdout);
#endif
}

const int N = 1e5 + 1e3, inf = 0x3f3f3f3f;

#define lson ls[o], l, mid
#define rson rs[o], mid + 1, r

template<int Maxn>
struct Chair_Man_Tree {

    int ls[Maxn], rs[Maxn], minv[Maxn], Size;

    Chair_Man_Tree() { minv[0] = inf; }

    inline int Newnode() {
        int o = ++ Size; minv[o] = inf; return o;
    }

    void Update(int &o, int l, int r, int up, int uv) {
        if (!o) o = Newnode();
        if (l == r) { chkmin(minv[o], uv); return ; }
        int mid = (l + r) >> 1;
        up <= mid ? Update(lson, up, uv) : Update(rson, up, uv);
        minv[o] = min(minv[ls[o]], minv[rs[o]]);
    }

    int Query(int o, int l, int r, int ql, int qr) {
        if (!o) return inf;
        if (ql <= l && r <= qr) return minv[o];
        int mid = (l + r) >> 1;
        if (qr <= mid) return Query(lson, ql, qr);
        if (ql > mid) return Query(rson, ql, qr);
        return min(Query(lson, ql, qr), Query(rson, ql, qr));
    }

    int Merge(int x, int y) {
        if (!x || !y) return x | y;
        int o = Newnode();
        ls[o] = Merge(ls[x], ls[y]);
        rs[o] = Merge(rs[x], rs[y]);
        minv[o] = min(minv[x], minv[y]);
        return o;
    }

};

vector<int> G[N]; int val[N], rt[N], dep[N];

int n, S;

Chair_Man_Tree<N * 80> T;

void Dfs(int u, int fa = 0) {
    dep[u] = dep[fa] + 1;
    for (int v : G[u]) if (v != fa)
        Dfs(v, u), rt[u] = T.Merge(rt[u], rt[v]);
    T.Update(rt[u], 1, n, dep[u], val[u]);
}

int main () {

    File();

    n = read(); S = read(); 

    For (i, 1, n)
        val[i] = read();
    
    For (i, 1, n - 1) {
        int u = read(), v = read();
        G[u].pb(v); G[v].pb(u);
    }

    Dfs(S);

    int q = read(), ans = 0;
    For (i, 1, q) {
        int x = (read() + ans) % n + 1, k = (read() + ans) % n;
        printf ("%d\n", ans = T.Query(rt[x], 1, n, dep[x], min(dep[x] + k, n)));
    }

    return 0;

}