P14989 传送 个人题解

题目链接

题目大意

现在有 \(n\) 颗星球,每颗星球上都有左右两座转送门,左边的传送门通往编号比当前星球小的第一个比当前星球大小大的星球(如果不存在就通往本身),右边的传送门通往编号比当前星球大的第一个比当前星球大小大的星球(如果不存在就通往本身),现在有 \(q\) 个任务,每个任务有 \(k\) 个小机器人,这些小机器人可以通过上述的左右传送门进行星球间的移动,问这 \(k\) 个小机器人可以在多少颗星球上相遇在一起。

Solution

首先我们可以发现,无论是左传送门还是右传送门,最终通向的都是大小比这颗星球大的星球,说明最后通过传送门到达的最终星球一定是最大的星球,那么我们如果按照左右传送门作为边把这些星球连接成一棵树,那么这个最大的星球就是这棵树的根节点。

然后我们要找 \(k\) 个小机器人能在哪些星球上相遇,上面我们已经根据边建立了一棵树,这些小机器人能到达的星球其实就是他所在的星球到根(最大的星球)的路径上的星球,要求 \(k\) 个小机器人能在哪里相遇,其实就是求在他们到根的路径上有几个重合的星球。那么现在问题就转化成求一棵树上几个节点的最近公共祖先问题(LCA)了(不会求树上 LCA 的可以先去看一下模板题),因为可以发现最近公共祖先是距离根节点最远的小机器人可以相遇的星球,而从最近公共祖先到根路径上的所有星球小机器人都可以相遇,最终答案就是最近公共祖先到根的路径长度,也就是最近公共最先的深度。

处理左右传送门的时候可以使用单调栈,当一个星球左右传送门都有的时候选择通往大小更小的星球作为父节点,这样能保证最大的星球也就是根的入度为 \(0\),并通过这个找到根。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+6;
inline int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0' || c>'9'){
		if(c=='-')
			f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}
int n=read(),q=read(),a[N],l[N],r[N],depth[N],fa[N][30];
vector<int> edges[N];
stack<int> s;
inline void build(){
    for(int i=1;i<=n;i++){//处理左传送门 
        while(!s.empty() && a[s.top()]<a[i]) s.pop();
        l[i]=s.empty()?0:s.top();
        s.push(i);
    }
    while(!s.empty()) s.pop();//清空单调栈 
    for(int i=n;i;i--){//处理右传送门 
        while(!s.empty() && a[s.top()]<a[i]) s.pop();
        r[i]=s.empty()?0:s.top();
        s.push(i);
	}
    for(int i=1;i<=n;i++){
        int u=l[i],v=r[i],father=0;
        if(!u && !v) continue;//如果左右都没有传送门直接跳过 
        else if(!u) father=v;
        else if(!v) father=u;
        else father=(a[u]<a[v])?u:v;//如果两边都有传送门,选择小的作为父节点 
        if(father){
        	edges[father].push_back(i);
			fa[i][0]=father;	
		}
    }
}
inline void dfs(int x,int father){//求倍增数组 
	depth[x]=depth[father]+1;
	for(int i=1;i<=20;i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for(auto y:edges[x]){
		if(y==father)
			continue;
		dfs(y,x);
	}
} 
inline int lca(int x,int y){//倍增求树上最近公共祖先 
	if(depth[x]<depth[y])
		swap(x,y);
	int k=depth[x]-depth[y];
	for(int j=0;j<=20 && k;j++,k/=2)
		if(k&1) x=fa[x][j];
	if(x==y) return x;
	for(int j=20;j>=0;j--)
		if(fa[x][j]!=fa[y][j]){
			x=fa[x][j];
			y=fa[y][j];
		}
	return fa[x][0];
}
int main(){
	for(int i=1;i<=n;i++)
		a[i]=read();
	build();
	int root=0;
	for(int i=1;i<=n;i++)//找到最大值根 
		if(!fa[i][0]) root=i;
	dfs(root,0);
	while(q--){
		int k=read(),Lca;
		for(int i=1;i<=k;i++){
			int x=read();
			if(i==1) Lca=x;
			else Lca=lca(Lca,x);	
		}
		printf("%d\n",depth[Lca]);
	}
	return 0;
}
posted @ 2026-01-18 19:14  See_you_soon  阅读(0)  评论(0)    收藏  举报