BZOJ2588Count on a tree——LCA+主席树

题目描述

给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权。其中lastans是上一个询问的答案,初始为0,即第一个询问的u是明文。

输入

第一行两个整数N,M。
第二行有N个整数,其中第i个整数表示点i的权值。
后面N-1行每行两个整数(x,y),表示点x到点y有一条边。
最后M行每行两个整数(u,v,k),表示一组询问。

输出

M行,表示每个询问的答案。最后一个询问不输出换行符

样例输入

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

提示

HINT:
N,M<=100000
 
  
  查询一个路径上第k小,就相当于查询序列上区间第k小,可以想到用主席树维护。但这道题是在树上完成的操作,所以并不能像平常主席树一样只用r时刻减掉l-1时刻的线段树查询。对于树上的路径(以x和y之间的路径为例),可以把它分成两部分:x到lca和y到lca。这样整条路径上的信息就可以通过这两条链相加得到。所以直接用x时刻线段树+y时刻线段树-lca时刻线段树-lca的父节点时刻线段树就得到路径上状态。每个时刻线段树由它的父节点时刻线段树转移过来。
最后附上代码。
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define mid (L+R)/2
using namespace std;
int ans;
int tot;
int cnt;
int anc;
int n,m,q;
int x,y,z;
int d[100010];
int v[100010];
int h[100010];
int l[3000010];
int r[3000010];
int to[200010];
int head[100010];
int next[200010];
int sum[3000010];
int root[100010];
int f[100010][20];
map<int,int>b;
void add(int x,int y)
{
    tot++;
    next[tot]=head[x];
    head[x]=tot;
    to[tot]=y;
}
int lca(int x,int y)
{
    if(d[x]<d[y])
    {
        swap(x,y);
    }
    int dep=d[x]-d[y];
    for(int i=0;i<=19;i++)
    {
        if((dep&(1<<i))!=0)
        {
            x=f[x][i];
        }
    }
    if(x==y)
    {
        return x;
    }
    for(int i=19;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
int updata(int pre,int L,int R,int k)
{
    int rt=++cnt;
    l[rt]=l[pre];
    r[rt]=r[pre];
    sum[rt]=sum[pre]+1;
    if(L==R)
    {
        return rt;
    }
    else
    {
        if(k<=mid)
        {
            l[rt]=updata(l[pre],L,mid,k);
        }
        else
        {
            r[rt]=updata(r[pre],mid+1,R,k);
        }
    }
    return rt;
}
int query(int x,int y,int anc,int fa,int L,int R,int k)
{
    if(L==R)
    {
        return b[L];
    }
    int num=sum[l[x]]+sum[l[y]]-sum[l[anc]]-sum[l[fa]];
    if(num>=k)
    {
        return query(l[x],l[y],l[anc],l[fa],L,mid,k);
    }
    else
    {
        return query(r[x],r[y],r[anc],r[fa],mid+1,R,k-num);
    }
}
void dfs(int x,int fa)
{
    d[x]=d[fa]+1;
    int k=lower_bound(h+1,h+1+m,v[x])-h;
    b[k]=v[x];
    root[x]=updata(root[fa],1,n,k);
    for(int i=1;i<=19;i++)
    {
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for(int i=head[x];i;i=next[i])
    {
        if(to[i]!=fa)
        {
            f[to[i]][0]=x;
            dfs(to[i],x);
        }
    }
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&v[i]);
        h[i]=v[i];
    }
    sort(h+1,h+1+n);
    m=unique(h+1,h+1+n)-h-1;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs(1,0);
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        x=x^ans;
        anc=lca(x,y);
        ans=query(root[x],root[y],root[anc],root[f[anc][0]],1,n,z);
        printf("%d\n",ans);
    }
}

转载于:https://www.cnblogs.com/Khada-Jhin/p/9298150.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值