P2633 Count on a tree 树上主席树

树上主席树

在树上根据dfs序建立主席树。
题目
给定一棵 n 个节点的树,每个点有一个权值。有 m 个询问,每次给你 u,v,k,你需要回答 u xor last 和 v 这两个节点间第 k 小的点权。
其中 last是上一个询问的答案,定义其初始为 0,即第一个询问的 u 是明文。
维护主席树,每个节点代表这个下标的数出现的次数。
用u点的主席树+v点的主席树-lca(u,v)的主席树-lca(u,v)父节点的主席树,在这样产生的主席树上查找第k小的排名。
注意
在dfs的时候就建立主席树,这样求的前缀和就是按照从根到该节点的值(用dfn数组建立的树是错的,太菜不知道为什么,也许是什么地方写错了)。
*标记了容易出错的地方。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 +7;
typedef long long ll;
int head[maxn], to[maxn<<1], nex[maxn<<1], tot, m, n, TT;
int val[maxn];
int tsize;
struct node {
    int l, r, sum;
}T[maxn * 20];
int v[maxn], root[maxn];
int getid(int x) {
    return lower_bound(v + 1, v + 1 + tsize, x) - v;
}
void add(int x, int y) {
    to[tot] = y;
    nex[tot] = head[x];
    head[x] = tot++;
}
int h[maxn], f[maxn][30];
void dfs(int x, int fx) {
    h[x] = h[fx] + 1;
    f[x][0] = fx;
    for (int i = 1; i <= TT; i++) f[x][i] = f[f[x][i - 1]][i - 1];
    for (int i = head[x]; ~i; i = nex[i])
        if(to[i] != fx) dfs(to[i], x);
}
int LCA(int x, int y) {
    if(h[x] > h[y]) swap(x, y);
    for (int i = TT; i >= 0; i--)
        if(h[f[y][i]] >= h[x]) y = f[y][i];
    if(x == y) return x;
    for (int i = TT; i >= 0; i--)
        if(f[y][i] != f[x][i])
            y = f[y][i], x = f[x][i];
    return f[x][0];
}
int ct;
void update(int l, int r, int &x, int y, int pos) {
    T[++ct] = T[y];
    T[ct].sum++;
    x = ct;
    if(l == r) return;
    int mid = (l + r) >> 1;
    if(pos <= mid) update(l, mid, T[x].l, T[y].l, pos);
    else update(mid + 1, r, T[x].r, T[y].r, pos);
}
int query(int l, int r, int x, int y, int z, int t, int k) {
    if(l == r) {
        return v[l];
    }
    int mid = (l + r) >> 1;
    int sum = T[T[x].l].sum + T[T[y].l].sum - T[T[z].l].sum - T[T[t].l].sum;
    if(sum >= k) return query(l, mid, T[x].l, T[y].l, T[z].l, T[t].l, k);
    else return query(mid + 1, r, T[x].r, T[y].r, T[z].r, T[t].r, k - sum);//*往右找就是找第k-sum小的数
}
void dfs(int u) {//*再来一遍dfs建立主席树
    update(1, tsize, root[u], root[f[u][0]], getid(val[u]));
    for (int i = head[u]; ~i; i = nex[i]) {
        int v = to[i];
        if(v != f[u][0]) dfs(v);
    }
}
int main()
{
    cin >> n >> m;
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= n; i++) scanf("%d", &val[i]), v[i] = val[i];
    sort(v + 1, v + 1 + n);
    tsize = unique(v + 1, v + 1 + n) - v - 1;
    for (int i = 1; i < n; i++) {
        int x, y;
        scanf("%d %d", &x, &y);
        add(x, y); add(y, x);
    }
    TT = int(log(n) / log(2)) + 1;//树的最大深度
    int u, v, k;
    dfs(1, 0);
    dfs(1);
    int ans = 0;
    for (int i = 1; i <= m; i++) {
        scanf("%d %d %d", &u, &v, &k);
        u ^= ans;
        int lca = LCA(u, v);
        ans = query(1, tsize, root[u], root[v], root[lca], root[f[lca][0]], k);
        printf("%d\n", ans);
    }
    return 0;
}
//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

//2
//8
//9
//105
//7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值