题目链接:https://www.luogu.org/problem/P2633
解题思路:
这道题目我是用主席树+lca过的;但是这道题目的主席树我们用到的序列的顺序是树dfs序去建立一颗主席树;和一般请款的序列顺序建立主席树不同;然后由于我们需要明确是在一条路径上去找第k小;所以我们需要找到直接连接到这2个点的一条路径,所以我们需要找到这2个点的最近公共祖先;当我们找到这个最近公共祖先之后,由于我们的主席树其实是维护了一个前缀和,所以我们只需要:sum(x) + sum(y) - sum(u) - sum(f[u][0]);就可以直接去查询其第k小的数值。
注意点:
1.由于是dfs序,所以用bfs也是可以的。
2.这道题目,我刚开始全部re,以为是数组开小了,(最开始我是用bfs写的,其实都一样);最后发现是我的bfs少了一行代码,忘了把1这个节点加进去。
代码:
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <queue>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5+ 5, M = 3e5 + 5;
queue<int> q;
int n, m, t, num, cnt;
int h[N], e[M], ne[M], idx;
int f[N][20], d[N];
int root[N];
LL w[N], v[N];
struct SegmentTree { int l, r, sum; }tree[N * 20];
inline void init(void) {
idx = cnt = 0;
memset(h, -1, sizeof h);
memset(d, 0, sizeof d);
}
inline int find(int l, int r, LL x) {
while(l < r) {
int mid = l + r >> 1;
if(v[mid] >= x) r = mid;
else l = mid + 1;
}
return l;
}
inline void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, ne[idx] = h[b], h[b] = idx ++;
}
inline void build(int &s, int l, int r) {
s = ++ cnt;
tree[s].sum = 0;
if(l == r) return;
int mid = l + r >> 1;
build(tree[s].l, l, mid);
build(tree[s].r, mid + 1, r);
}
inline void insert(int l, int r, int &x, int y, int pos) {
x = ++ cnt, tree[cnt] = tree[y], tree[cnt].sum ++, x = cnt;
if(l == r) return;
int mid = l + r >> 1;
if(pos <= mid) insert(l, mid, tree[x].l, tree[y].l, pos);
else insert(mid + 1, r, tree[x].r, tree[y].r, pos);
}
inline void bfs(void) {
while(q.size()) q.pop();
q.push(1), d[1] = 1;
insert(1, num, root[1], root[0], w[1]);
while(q.size()) {
int u = q.front(); q.pop();
for(int i = h[u]; i + 1; i = ne[i]) {
int v = e[i];
if(d[v]) continue;
d[v] = d[u] + 1;
insert(1, num, root[v], root[u], w[v]);
f[v][0] = u;
for(int j = 1; j <= t; j ++)
f[v][j] = f[f[v][j - 1]][j - 1];
q.push(v);
}
}
}
inline void dfs(int u, int fa) {
insert(1, num, root[u], root[fa], w[u]);
f[u][0] = fa;
d[u] = d[fa] + 1;
for(int i = 1; i <= t; i ++)
f[u][i] = f[f[u][i - 1]][i - 1];
for(int i = h[u]; i + 1; i = ne[i]) {
int v = e[i];
if(v == fa) continue;
dfs(v, u);
}
}
inline int lca(LL x, LL y) {
if(d[x] > d[y]) swap(x, y);
for(int i = t; i >= 0; i --)
if(d[f[y][i]] >= d[x]) y = f[y][i];
if(x == y) return x;
for(int i = t; i >= 0; i --)
if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
inline int query(int l, int r, int y, int x, int yy, int xx, int k) {
if(l == r) return l;
int mid = l + r >> 1;
int sum = tree[tree[y].l].sum + tree[tree[yy].l].sum - tree[tree[x].l].sum - tree[tree[xx].l].sum;
if(k <= sum) return query(l, mid, tree[y].l, tree[x].l, tree[yy].l, tree[xx].l, k);
else return query(mid + 1, r, tree[y].r, tree[x].r, tree[yy].r, tree[xx].r, k - sum);
}
int main(void) {
// freopen("in.txt", "r", stdin);
scanf("%d%d", &n, &m);
init();
t = (int)(log(n) / log(2)) + 1;
for(int i = 1; i <= n; i ++) scanf("%lld", &w[i]), v[i] = w[i];
sort(v + 1, v + 1 + n);
num = unique(v + 1, v + 1 + n) - (v + 1);
for(int i = 1; i <= n; i ++)
w[i] = find(1, num, w[i]);
for(int i = 1; i <= n - 1; i ++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
build(root[0], 1, num);
bfs();
// dfs(1, 0);
LL permt = 0;
for(int i = 1; i <= m; i ++) {
LL x, y;
int k;
scanf("%lld%lld%d", &x, &y, &k);
x = x ^ permt;
int u = lca(x, y);
int tmp = query(1, num, root[x], root[u], root[y], root[f[u][0]], k);
printf("%lld\n", v[tmp]);
permt = v[tmp];
}
return 0;
}
总结:对于静态维护主席树,我感觉其实难点并不是代码有多难写,而是我们需要考虑用怎样的序列顺序去建立一个主席树来刚好满足题目中所需。