洛谷P2633 Count on a tree 题解

洛谷P2633 Count on a tree 题解

题目链接:P2633 Count on a tree

题意:给定一棵树和 u , v , k u,v,k u,v,k ,求 u , v u,v u,v 结点间的第 k k k 小点权

本来以为是个树链剖分+主席树,结果发现并不可做

观察到主席树本质上利用了前缀和来计算线性的区间和

现在就是把求和搬到了树上

可以立即想到树上差分

不过这道题不是树上差分,可能应该叫树上前缀和更恰当一些

对于一条根结点(无根树就假定是 1 1 1 好了)到叶子结点的简单路径,就是一个线性区间上的主席树

也就是在树上建立了类似前缀和体系的主席树群,每一个主席树维护了一条根结点到叶子结点的路径上的结点的信息

感性理解一下,有点“前缀主席树”的感觉

那么 u u u v v v 的信息就是

sum[u]+sum[v]-sum[lca(u,v)]-sum[fa[lca(u,v)][0]]

是不是很像树上差分了

其他的比如离散化什么的都是基本操作了,这里不再赘述了 qwq

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

代码如下

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define gc() getchar()
#define pc(a) putchar(a)
template<typename T>inline void read(T &k)
{
	char ch=gc();T x=0,f=1;
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
	k=x*f;
}
template<typename T>inline void write(T k)
{
	if(k<0){k=-k;pc('-');}if(k>9)write(k/10);pc(k%10+'0');
}
#define MAXN (int)(1e5+5)
struct node
{
	int u,v,next;
}e[MAXN<<1];
int n,m,Q,last,a[MAXN],b[MAXN],tot;
int head[MAXN],pos=1,fa[MAXN][22],lg[MAXN];
int L[MAXN<<5],R[MAXN<<5],sum[MAXN<<5],T[MAXN<<5],dep[MAXN];
void addEdge(int u,int v)
{
	e[pos]={u,v,head[u]};
	head[u]=pos++;
}
int build(int l,int r)
{
	int at=++tot;
	if(l<r)
	{
		int mid=(l+r)>>1;
		L[at]=build(l,mid);
		R[at]=build(mid+1,r);
	}
	return at;
}
int insert(int pre,int l,int r,int k)
{
	int at=++tot;
	L[at]=L[pre];R[at]=R[pre];sum[at]=sum[pre]+1;
	if(l<r)
	{
		int mid=(l+r)>>1;
		if(k<=mid)L[at]=insert(L[pre],l,mid,k);
		else R[at]=insert(R[pre],mid+1,r,k);
	}
	return at;
}
void dfs(int u,int f)
{
	fa[u][0]=f;dep[u]=dep[f]+1;
	T[u]=insert(T[f],1,m,a[u]);
	for(int i=1; i<=lg[dep[u]]; i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int i=head[u]; i; i=e[i].next)
	{
		int v=e[i].v;
		if(v==f)continue;
		dfs(v,u);
	}
}
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 i=lg[dep[x]]; i>=0; i--)
	{
		if(fa[x][i]!=fa[y][i])
			x=fa[x][i],y=fa[y][i];
	}
	return fa[x][0];
} // 倍增求LCA
int query(int x,int y,int z,int t,int l,int r,int k)
{
	if(l==r)return l;
	int mid=(l+r)>>1;
	int res=sum[L[x]]+sum[L[y]]-sum[L[t]]-sum[L[z]];
	if(k<=res)return query(L[x],L[y],L[z],L[t],l,mid,k);
	else return query(R[x],R[y],R[z],R[t],mid+1,r,k-res);
}
void init()
{
	sort(b+1,b+1+n);
	m=unique(b+1,b+1+n)-b-1;
	for(int i=1; i<=n; i++)
		a[i]=lower_bound(b+1,b+1+m,a[i])-b;
	for(int i=1; i<=n; i++)
		lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	build(1,m);
	dfs(1,0);
}
signed main()
{
	read(n);read(Q);
	for(int i=1; i<=n; i++)
		read(a[i]),b[i]=a[i];
	for(int i=1,u,v; i<n; i++)
	{
		read(u);read(v);
		addEdge(u,v);addEdge(v,u);
	}init();
	while(Q--)
	{
		int x,y,k;
		read(x);read(y);read(k);x^=last; // 强制在线
		int t=lca(x,y);
		write(last=b[query(T[x],T[y],T[fa[t][0]],T[t],1,m,k)]);pc('\n');
	}
	return 0;
}

转载请说明出处

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值