Count on a tree - 洛谷
## 题目描述
给定一棵 n 个节点的树,每个点有一个权值。有 m 个询问,每次给你 u,v,k,你需要回答 $u \text{ xor last和 v 这两个节点间第 k 小的点权。
其中 last 是上一个询问的答案,定义其初始为 0,即第一个询问的 u 是明文。
## 输入格式
第一行两个整数 $n,m$。
第二行有 $n$ 个整数,其中第 $i$ 个整数表示点 $i$ 的权值。
后面 $n-1$ 行每行两个整数 $x,y$,表示点 $x$ 到点 $y$ 有一条边。
最后 $m$ 行每行两个整数 $u,v,k$,表示一组询问。
## 输出格式
$m$ 行,每行一个正整数表示每个询问的答案。
题解:
刚刚学会主席树,当练手题了。
首先还是要求一个第k小,那么只能用主席树处理。
但是比较毒瘤的是我们平时用主席树都是处理序列信息,这里是树上信息。
我们自然希望能把树上信息转化成序列信息来求解。
那么又到了经典的两个节点之间的路径,可以转化为根节点到两个节点的路径-根节点到LCA。
那么此时我们就可以清晰地明白,只要把每根节点往下的每条路径的权值按dfs的顺序插入主席树即可。
这道题也让我对主席树有了更深刻的理解啊。
ps:
我们每次插入一个新的点,都维护的是根节点到这个节点的路径信息。
由rt可访问此前缀树。
询问时向左就一起向左,向右就一起向右,(这个点的值)有就有,没有就没有。
不会有什么路径替换。
因为每次更新都是从根节点开始的新建的logn子树。(长度固定,而且是上一次的树的节点分裂来的)
如果加了最小的,那么就这么拓展:
其他的差不多这么个意思,懒得画了。(怕我自己不会
参考代码:
/*keep on going and never give up*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define fast std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
const int maxn=2e5+10;
int n,m,s;
int rt[maxn<<4],ls[maxn<<4],rs[maxn<<4],sum[maxn<<4],tot;
int a[maxn],id[maxn],len;
int get_id(int x){ return lower_bound(id+1,id+1+len,x)-id; }//离散化,由下标建树,输出按id[]输出即可。
int bd(int l,int r){
int rt=++tot;
if(l==r) return rt;
int mid=l+r>>1;
ls[rt]=bd(l,mid);rs[rt]=bd(mid+1,r);
return rt;
}
int ad(int rt,int l,int r,int k){
int now=++tot;
ls[now]=ls[rt],rs[now]=rs[rt],sum[now]=sum[rt]+1;//分点,先继承原点的儿子,sum多一个,递归再处理儿子边界。
if(l==r) return now;
int mid=l+r>>1;
if(k<=mid) ls[now]=ad(ls[now],l,mid,k);// 往左边偏的话,左边界就要换新的子节点,右端点沿用之前的即可。
else rs[now]=ad(rs[now],mid+1,r,k);//右边同理。
return now;
}
void ini(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],id[i]=a[i];
sort(id+1,id+1+n);
len=unique(id+1,id+1+n)-id-1;
rt[0]=bd(1,len);// 最开始要根据原序列建一次树,留下最基本的节点左右信息。
// for(int i=1;i<=n;i++) rt[i]=add(rt[i-1],1,len,get_id(a[i]));//然后每次从上一次的根节点开始logn加节点.
// 每次加出来的新树都是序列到目前i为止的构成的树,亦为前缀树。
}
int dep[maxn],fa[maxn][22],lg[maxn];
struct node{
int to,nxt;
}ed[maxn<<1];int ehead[maxn],tot1;
struct gg{
void add(int x,int y){ ed[++tot1]={y,ehead[x]};ehead[x]=tot1; }
void init(){ for(int i=1;i<=n;i++) lg[i]=lg[i-1]+(1<<lg[i-1]==i); }// i是2的几次方
void dfs(int x,int pre) {
rt[x]=ad(rt[pre],1,len,get_id(a[x]));
fa[x][0]=pre;dep[x]=dep[pre]+1;
for(int i=1;i<=lg[dep[x]];i++)
fa[x][i]=fa[fa[x][i-1]][i-1]; //2<<(i-1)+2<<(i-1)
for(int i=ehead[x];i;i=ed[i].nxt){
if(ed[i].to==pre) continue;
int v=ed[i].to;
dfs(ed[i].to,x);
}
}
int lca(int x,int y) {
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y])
x=fa[x][lg[dep[x]-dep[y]]-1]; //跳到同一深度
if(x==y) return x;
for(int k=lg[dep[x]]-1;k>=0;k--)
if(fa[x][k]!=fa[y][k])
x=fa[x][k],y=fa[y][k];
return fa[x][0];
}
}LCA;
int ask(int u,int v,int _lca,int _fa_lca,int l,int r,int k){
int mid=l+r>>1,x=sum[ls[v]]+sum[ls[u]]-sum[ls[_lca]]-sum[ls[_fa_lca]];//算左边
if(l==r) return l;
if(k<=x) return ask(ls[u],ls[v],ls[_lca],ls[_fa_lca],l,mid,k);//在左边找;
return ask(rs[u],rs[v],rs[_lca],rs[_fa_lca],mid+1,r,k-x);//在右边找 k-x ;
//k min 一样的思想
}
signed main(){
fast
ini();LCA.init();int x,y,k;
for(int i=1;i<n;i++){
cin>>x>>y;
LCA.add(x,y);LCA.add(y,x);
}LCA.dfs(1,0);
int lastans=0;
for(int i=1;i<=m;i++){
cin>>x>>y>>k;
x^=lastans;
int d=LCA.lca(x,y);
int dd=ask(rt[x],rt[y],rt[d],rt[fa[d][0]],1,len,k);
lastans=id[dd];
cout<<lastans<<endl;
}
}