P2633 Count on a tree (树上主席树 LCA)



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;
	}
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值