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子树。(长度固定,而且是上一次的树的节点分裂来的)
如果加了最小的,那么就这么拓展:
其他的差不多这么个意思,懒得画了。(怕我自己不会
参考代码:#includeusing 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< 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 >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<



