1. 程式人生 > 實用技巧 >洛谷[P2633]Count on a tree「主席樹」

洛谷[P2633]Count on a tree「主席樹」

洛谷[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;
}