洛谷[P2633]Count on a tree「主席樹」
阿新 • • 發佈:2020-07-23
洛谷[P2633]Count on a tree「主席樹」
題目描述
給定一棵 \(n\) 個節點的樹,每個點有一個權值。有 \(m\)個詢問,每次給你 \(u,v,k\),你需要回答 \(u\) \(xor\) \(last\) 和 \(v\) 這兩個節點間第\(k\) 小的點權。
其中 \(last\) 是上一個詢問的答案,定義其初始為 \(0\),即第一個詢問的 \(u\) 是明文。
輸入格式
第一行兩個整數 \(n,m\)。
第二行有 \(n\) 個整數,其中第 \(i\) 個整數表示點 \(i\) 的權值。
後面 \(n−1\) 行每行兩個整數 \(x,y\),表示點 \(x\)到點 \(y\)
最後 \(m\) 行每行兩個整數 \(u,v,k\),表示一組詢問。
輸出格式
\(m\) 行,每行一個正整數表示每個詢問的答案。
輸入輸出樣例
輸入 #1
8 5
105 2 9 3 8 5 7 7
1 2
1 3
1 4
3 5
3 6
3 7
4 8
2 5 1
0 5 2
10 5 3
11 5 4
110 8 2
輸出 #1
2
8
9
105
7
說明/提示
【資料範圍】
對於 \(100%\) 的資料,\(1≤n,m≤10^{5}\)。
思路分析
又是因為一道題而對演算法有了更深刻的認識
- 區間第k小,顯然主席樹(其他的方法本蒟蒻暫且還不會)
- 關鍵點是,主席樹雖然說是樹,但它到底還是線段樹的變形,線段樹處理的是什麼?一個序列啊,可這道題確是在樹上進行操作,還能用嗎?這就需要對主席樹有透徹的理解(這也是我
看很多題解
關於主席樹:
- 作為可持續化衍生出的資料結構,主席樹的核心之一就是多個歷史版本,這個應該不用多說了,關鍵是另一個核心——字首和
- 實現:每個節點根據前一個節點建立。然後利用字首和思想拿第\(r\)棵樹 - 第\(l-1\)棵樹得到\([l,r]\)區間的資訊,操作在這棵新樹上操作即可。
- 那麼如果是一個線性的序列,我們只需要將其離散化後對每個點建立權值線段樹即可,然後根據字首和思想即可求解(推薦這個板子題P3834 【模板】可持久化線段樹 2(主席樹))
- 還是回到這個問題,樹上進行操作該如何處理?考慮樹的結構,樹……邊……節點……(突然明瞭)我們對每個節點在它父親基礎上建樹
- 接下來類似於序列的處理,將樹上分為幾個鏈,用到樹鏈剖分,那x-y路徑的資訊就是\(siz[u]+siz[v]−siz[lca(u,v)]−siz[fa[lca(u,v)]]\)。
Code
//註釋自己加的,是要寫題解,而不是粘的題解
#include<bits/stdc++.h>
#define N 200010
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
struct Tree{
int l, r, sum;
}tree[N<<5];
struct edge{
int next, to;
}e[N * 2];
int n, m,tot,cnt,dex,last;
int h[N], a[N], b[N], fa[N], dep[N];
int son[N], top[N], size[N], rt[N];
int find(int x){
return lower_bound(b + 1, b + 1 + cnt, x) - b;
}
void add(int u, int v){
e[++tot].next = h[u];
e[tot].to = v;
h[u] = tot;
}
void dfs1(int x, int fath, int depth){ //兩個dfs跑樹剖
size[x] = 1, fa[x] = fath,dep[x] = depth;
int maxSon = 0;
for(int i = h[x]; i != 0; i = e[i].next)
if(e[i].to != fath){
dfs1(e[i].to, x, depth + 1);
size[x] += size[e[i].to];
if(size[e[i].to] > maxSon){
maxSon = size[e[i].to];
son[x] = e[i].to;
}
}
}
void dfs2(int x, int head){
top[x] = head;
if(!son[x]) return;
dfs2(son[x], head);
for(int i = h[x]; i != 0; i = e[i].next)
if(e[i].to != fa[x] && e[i].to != son[x])
dfs2(e[i].to, e[i].to);
}
int lca(int x, int y){//樹剖求lca
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(dep[x] < dep[y]) return x;
return y;
}
int build(int l, int r){
int p = ++dex;//開點
int mid = (l + r) >> 1;
if(l == r) return p; //葉子節點
tree[p].l = build(l, mid);
tree[p].r = build(mid + 1, r);
return p;
}
int update(int pre, int l, int r, int val){
int p = ++dex;//還是開點
int mid = (l + r) >> 1;
tree[p].l = tree[pre].l;//複製上一個版本
tree[p].r = tree[pre].r;
tree[p].sum = tree[pre].sum + 1;//這個當然不能複製啦
if(l == r) return p;
if(val <= mid) tree[p].l = update(tree[pre].l, l, mid, val);
else tree[p].r = update(tree[pre].r, mid + 1, r, val);
return p;
}
void dfs(int x){
rt[x] = update(rt[fa[x]], 1, cnt, find(a[x]));
for(int i = h[x]; i != 0; i = e[i].next){
if(e[i].to != fa[x]){
dfs(e[i].to);
}
}
}
int query(int s1, int s2, int fa, int pa, int l, int r, int rank){
int size = tree[tree[s2].l].sum + tree[tree[s1].l].sum - tree[tree[fa].l].sum - tree[tree[pa].l].sum;
int mid = (l + r) >> 1;
if(l == r) return l;
if(rank <= size) return query(tree[s1].l, tree[s2].l, tree[fa].l, tree[pa].l, l, mid, rank);
else return query(tree[s1].r, tree[s2].r, tree[fa].r, tree[pa].r, mid + 1, r, rank - size);
}
int main(){
n = read(),m = read();
for(int i = 1; i <= n; i++){
a[i] = read();
b[++cnt] = a[i];
}
sort(b + 1, b + 1 + cnt);
cnt = unique(b + 1, b + 1 + cnt)-b-1; //記得離散化
for(int i = 1; i < n; i++){
int u = read(), v = read();
add(u, v),add(v, u);
}
rt[0] = build(1, cnt);
dfs1(1, 0, 1), dfs2(1, 1), dfs(1);
for(int i = 1; i <= m; i++){
int u = read() ^ last, v = read(), rank = read();
int head = lca(u, v);
int id = query(rt[u], rt[v], rt[head], rt[fa[head]], 1, cnt, rank);
printf("%d\n",b[id]);
last = b[id];
}
return 0;
}