题解:P14989 传送
elainya_stars · · 题解
P14989 传送 题解
天哪竟然场切了,猫猫留下了欣慰的泪水 /ll
题意
给定
一定要分清值和下标,因为我赛时因为这个浪费了一个小时呜呜。
思路
看到跳公共点第一想法是 LCA 吧,不会的参见这里,所有的
一、找目标点
我们首先处理怎么找到每个点左面或右面的目标点,这个貌似是单调栈板子,不会的参见这里,从左往右遍历求左目标点,从右往左遍历求右目标点,中间记得清空栈。初始化完了,得到两个数组
二、贪心
其次,每个点左右都能跳,这让我们很头疼。但是仔细想想,其实只用往两边目标点的值最小的那一边跳就可以了,证明一下。
设当前点是 其实赛时是猜的)
三、建树
所以每个点都只能往一个点跳了。这回好建树了。在每个点的值
四、LCA
首先,把输入的
剩余亿点细节放注释里了,祝您阅读愉快。
Code
成功抢到最劣解。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+5,logn=20,inf=0x3f3f3f3f3f3f3f3f;
int n,q,p[N],pre[N],cur;
int dep[N],fa[N]; // dep是深度,fa是父节点,用来ST表
int lp[N],rp[N];
int st[N][25]; // ST表
stack<int> s; // 单调栈
struct edge // 链式前向星存图
{
int l,r,nxt;
void ae(int u,int v) // 加边
{l=u,r=v,nxt=pre[u],pre[u]=cur;}
}e[N*2];
void dfs(int x,int f) // 用来求每个点深度+父节点
{
fa[x]=f,dep[x]=dep[f]+1;
for(int i=pre[x];i;i=e[i].nxt)
{
int r=e[i].r;
if(r==f)
continue;
dfs(r,x);
}
}
void set_st() // LCA板子
{
for(int i=1;i<=n;i++)
st[i][0]=fa[i];
for(int j=1;j<=logn;j++)
for(int i=1;i<=n;i++)
st[i][j]=st[st[i][j-1]][j-1];
}
int lca(int x,int y) // LCA板子
{
if(dep[x]<dep[y])
swap(x,y);
for(int j=logn;j>=0;j--)
if(dep[st[x][j]]>=dep[y])
x=st[x][j];
if(x==y)
return x;
for(int j=logn;j>=0;j--)
if(st[x][j]!=st[y][j])
x=st[x][j],y=st[y][j];
return fa[x];
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
// 不加优化容易爆 TLE,见后文 Update 内容
cin>>n>>q;
for(int i=1;i<=n;i++)
cin>>p[i];
for(int i=1;i<=n;i++) // 单调栈求左目标点
{
while(!s.empty() && p[s.top()]<=p[i])
s.pop();
lp[i]=s.empty() ? inf : p[s.top()];
// 存的是值!!所以用p[]包起来
// 栈空代表没有左目标点,放最大值(因为建树时取的lp rp的min)
s.push(i);
}
while(!s.empty()) // 记得清空!
s.pop();
for(int i=n;i>0;i--) // 右目标点
{
while(!s.empty() && p[s.top()]<=p[i])
s.pop();
rp[i]=s.empty() ? inf : p[s.top()];
// 同上,放值
s.push(i);
}
for(int i=1;i<=n;i++)
{
int minn=min(lp[i],rp[i]); // 取min
if(minn!=inf) // 如果==inf说明左右都没有目标点,那建不了边
{
e[++cur].ae(minn,p[i]);
e[++cur].ae(p[i],minn); // 双向
}
}
dfs(n,0); // 一定先dfs再建ST表!不然没有每个点的父节点
set_st(); // 建ST表
while(q--)
{
int k,now;
cin>>k>>now;
now=p[now]; // 换成值!!!
if(k==1) // 只有它一个点就直接输出深度
{
cout<<dep[now]<<'\n';
continue;
}
// 现在,now的身份是当前的最近公共祖先
for(int i=2;i<=k;i++)
{
int x;
cin>>x;
x=p[x]; // 换成值!!!
now=lca(now,x); // 之前求好的祖先和新进的点求一次lca,更新当前祖先
}
cout<<dep[now]<<'\n'; // 输出求好的祖先的深度
// while(q--)这一部分如果没听懂回复文章问我,我基本在线
}
return (0.0);
}
给我赞赞 qwq
Update
2026.1.19 发现代码的一处 RE 错误,我在赛后优化代码时笔误。
2026.1.19 加入输入输出优化,因为赛时没写优化 993ms 卡过了赛后发现 TLE 然后紧急加上。
2026.1.20 发现一处时间复杂度计算错误,太糖了,已更正。