树上主席树(洛谷 P2633 Count on a tree)

题目链接
学了学树上主席树。之前一直以为树上主席树必须和树链剖分结合,学了之后发现不用。直接按照自然dfs序建树。每个节点都继承自它的fa节点。然后我们查询某路径(x,y):
t r [ x ] . s u m + t r [ y ] . s u m − t r [ l c a ( x , y ) ] . s u m − t r [ f a ( l c a ( x , y ) ) ] . s u m tr[x].sum + tr[y].sum - tr[lca(x,y)].sum - tr[fa(lca(x,y))].sum tr[x].sum+tr[y].sumtr[lca(x,y)].sumtr[fa(lca(x,y))].sum
比较好理解,可加运算从线性到树状的拓展。
下面是ac代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 2e5+5;
int root[N], cnt;
int lisan[N];
int su[N];
int mx;
int tot = 1;
int he[N], ne[N<<1], ver[N<<1];
int f[N][20];
int dep[N];
void add(int x, int y)
{
    ne[++tot] = he[x];
    ver[tot] = y;
    he[x] = tot;
}
struct Node
{
    int l, r, sum;
}tr[N<<5];
inline int getn(int g)
{
    return lower_bound(lisan+1, lisan+mx, g) - lisan;
}
void change(int l, int r, int &p, int pre, int v)
{
    tr[++cnt] = tr[pre];
    p = cnt;
    tr[p].sum++;
    if (l == r) return;
    int mid = (l+r) >>1;
    if (v <= mid) change(l, mid, tr[p].l, tr[pre].l, v);
    else change(mid+1, r,tr[p].r, tr[pre].r, v);
}
void dfs(int u, int fa)
{
    change(1, mx-1, root[u], root[fa], getn(su[u]));
    f[u][0] = fa;
    dep[u] = dep[fa] + 1;
    for (int i = 1; i <= 18; i++)
        f[u][i] = f[f[u][i-1]][i-1];
    for (int i = he[u]; i; i = ne[i])
    {
        int y = ver[i];
        if (y == fa) continue;
        dfs(y, u);
    }
}
int lca(int x, int y)
{
    if (dep[x] > dep[y]) swap(x, y);
    for (int i = 18; i >= 0; i--)
    {
        if (dep[f[y][i]] < dep[x]) continue;
        y = f[y][i];
    }
    if (x == y) return x;
    for (int i = 18; i >= 0; i--)
        if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    return f[x][0];
}
int ask(int l, int r, int x, int y, int z, int w, int k)
{
    if (l == r) return l;
    int sum = tr[tr[x].l].sum + tr[tr[y].l].sum - tr[tr[z].l].sum - tr[tr[w].l].sum;
    int mid = (l + r) >> 1;
    if (sum >= k) return ask(l, mid, tr[x].l, tr[y].l, tr[z].l, tr[w].l, k);
    return ask(mid+1, r, tr[x].r, tr[y].r, tr[z].r, tr[w].r, k-sum);
}
int mask(int x, int y, int k)
{
    int _lca = lca(x, y);
    return lisan[ask(1, mx-1, root[x], root[y], root[_lca], root[f[_lca][0]], k)];
}
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &su[i]);
        lisan[++mx] = su[i];
    }
    sort(lisan+1, lisan+mx+1);
    mx = unique(lisan+1, lisan+mx+1) - lisan;
    for (int i= 1; i < n; i++)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y); add(y, x);
    }
    dfs(1, 0);
    int la = 0;
    while(m--)
    {
        int x, y, k;
        scanf("%d%d%d", &x, &y, &k);
        printf("%d\n", la = mask(x^la, y, k));
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值