query can be restored as follows:
Let last���� be the answer for previous query (or 00 if i=1� = 1). Then xi=((pi+last)modn)+1�� = ((�� + ����) mod �) + 1, and ki=(qi+last)modn�� = (�� + ����) mod �.
Output
Print m� integers. i�-th of them has to be equal to the answer to i�-th query.
Example
Input
5 2 1 3 2 3 5 2 3 5 1 3 4 4 1 2 1 2 2 3Output
2 5解题思路
本题的每次询问相当于在以 x� 为根的子树中,求与 x� 距离不超过 k� 的节点权值的最小值。通过引入 DFS 序,我们可以将树上的子树问题转化为序列上的区间问题。设节点 x� 对应的 DFS 序区间为 [lx,rx][��,��],则合法节点 y� 的 DFS 序需满足 lx≤ly≤rx��≤��≤��。定义 dx�� 为节点 x� 到根节点的距离,由于 y� 是 x� 的后代,两者间的距离即为深度差 dy−dx��−��,因此距离不超过 k� 等价于 dy−dx≤k��−��≤�,即 dy≤dx+k��≤��+�。综上,所求点集可表示为满足 lx≤ly≤rx��≤��≤�� 且 dy≤dx+k��≤��+� 的节点 y� 的集合。
虽然题目要求强制在线处理查询,但我们可以先考虑离线做法以寻求启发。在给定 x� 和 k� 时,约束 dy≤dx+k��≤��+� 意味着合法节点 y� 的深度不超过某一固定上界。因此,我们只需在所有深度不超过 dx+k��+� 且 DFS 序落在 [lx,rx][��,��] 内的节点中寻找最小点权。如果对每个询问单独处理显然会超时,但若允许离线,我们可将询问按 dx+k��+� 升序排序,并按此顺序处理。在处理当前询问前,将深度不超过 dx+k��+� 的所有节点插入线段树(插入位置为节点的 DFS 序,值为其权值)。此时线段树中恰好包含了所有深度不超过 dx+k��+� 的节点,直接查询区间 [lx,rx][��,��] 的最小值即为该询问的答案。
对于在线查询而言,我们无法为每个询问重新构建线段树,但如果能快速获取维护了深度不超过 dx+k��+� 的所有节点的线段树,就能快速查询答案。一个自然的想法是直接保存每个深度对应的线段树,这便引出了可持久化线段树的应用。由于深度为 d� 的线段树是在深度为 d−1�−1 的线段树基础上扩展而来,我们只需在上一深度的历史版本上依次插入当前深度的节点,即可构建出当前深度的版本。只需在处理询问前通过预处理构建可持久化线段树即可,时间和空间复杂度均为 O(nlogn)�(�log�)。
这种利用可持久化线段树扩展历史版本的思路,与 G - Copy Query 相似。
AC 代码如下,时间复杂度为 O((n+q)logn)�((�+�)log�):
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N = 1e5 + 5, M = N * 2; int a[N]; int h[N], e[M], ne[M], idx; int tin[N], tout[N], d[N]; int p[N]; struct Node { int l, r, mn; }tr[N * 35]; int root[N]; void add(int u, int v) { e[idx] = v, ne[idx] = h[u], h[u] = idx++; } void dfs(int u, int p) { tin[u] = ++idx; d[u] = d[p] + 1; for (int i = h[u]; i != -1; i = ne[i]) { int v = e[i]; if (v == p) continue; dfs(v, u); } tout[u] = idx; } int modify(int u, int l, int r, int x, int c) { int v = ++idx; tr[v] = tr[u]; if (l == r) { tr[v].mn = c; } else { int mid = l + r >> 1; if (x <= mid) tr[v].l = modify(tr[u].l, l, mid, x, c); else tr[v].r = modify(tr[u].r, mid + 1, r, x, c); tr[v].mn = min(tr[tr[v].l].mn, tr[tr[v].r].mn); } return v; } int query(int u, int l, int r, int ql, int qr) { if (l >= ql && r <= qr) return tr[u].mn; int mid = l + r >> 1; if (qr <= mid) return query(tr[u].l, l, mid, ql, qr); if (ql >= mid + 1) return query(tr[u].r, mid + 1, r, ql, qr); return min(query(tr[u].l, l, mid, ql, qr), query(tr[u].r, mid + 1, r, ql, qr)); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; for (int i = 1; i <= n; i++) { cin >> a[i]; } memset(h, -1, sizeof(h)); for (int i = 0; i < n - 1; i++) { int u, v; cin >> u >> v; add(u, v), add(v, u); } idx = 0; dfs(m, 0); iota(p + 1, p + 1 + n, 1); sort(p + 1, p + n + 1, [&](int i, int j) { return d[i] < d[j]; }); idx = 0; tr[0].mn = 0x3f3f3f3f; for (int i = 1; i <= n; i++) { root[d[p[i]]] = modify(root[d[p[i - 1]]], 1, n, tin[p[i]], a[p[i]]); } int ret = 0; cin >> m; while (m--) { int x, k; cin >> x >> k; x = (x + ret) % n + 1, k = (k + ret) % n; ret = query(root[min(d[p[n]], d[x] + k)], 1, n, tin[x], tout[x]); cout << ret << '\n'; } return 0; }对本题进一步扩展。假设每次查询的是在以 x� 为根的子树中,求与 x� 距离至少为 lb�� 且不超过 ub�� 的节点权值最小值,约束条件将变为 lx≤ly≤rx��≤��≤�� 且 dx+lb≤dy≤dx+ub��+��≤��≤��+��。在强制在线的情况下,上述的可持久化线段树做法不再适用,可能需要用树套树来维护深度与 DFS 序的两维约束,由于博主不了解这里暂不展开讨论。若允许离线处理,则可使用 dsu on tree 来解决。
具体而言,先将询问按节点 x� 分类,分发到每个对应的节点。在 dfs 遍历树的过程中,对于当前节点 x�,先求解其所有轻儿子的答案,再求解重儿子的答案。此时利用线段树维护重儿子对应子树中各节点在其深度位置上的权值,接着将轻儿子对应子树的节点也插入该线段树。最后,对于当前节点 x� 的所有询问,直接在维护好