P7518 [省选联考 2021 A/B 卷] 宝石
题目大意
详细题目传送门
给出一棵树,树有点权,点权大小不超过 \(m\),\(Q\) 组询问,每组询问 \(s,t\)。
给出 \(P_1\cdots P_c\)。
求最大的 \(v\) 使存在 \(s\rightarrow t\) 路径上的一个子序列匹配 \(P_1\cdots P_v\)。
\(n\leq 2\cdot 10^5,c\leq m\leq 5\cdot 10^4\),\(P\) 互不相同。
思路
首先可以先将 \(P\) 变成 \([1,n]\) 的序列,再对点权做同样转化,这样就变成了求路径形如 \(1,2,3,\cdots\) 的最长子序列长度。通常这种题肯定是转化成记录 \(s\rightarrow lca(s,t)\) 的贡献和 \(lca(s,t)\rightarrow t\) 的贡献。
发现无论是要求还是难易度肯定是前者好求,于是先思考前者。对于朴素做法,直接一个个跳是不好的。发现我们首先可以尝试不去在所有情况下都一个个去跳。尝试对于每一个 \(u\),维护一个 \(X_u\) 表示 \(u\) 的祖先当中点权为 \(w_u+1\) 且深度最大的那一个。于是我们就可以从 \(s\) 的祖先中找到最深的 \(w_u=1\) 的点,然后每一次 \(u=X_u\) 看到 \(lca(s,t)\) 之前能够跳到哪里。然后就是在深搜时维护一个 \(cls_v\) 表示在 \(u\) 的祖先当中深度最大且 \(w_u=v\) 的点。发现搜到一个 \(u\) 时,在子树中没有找到一个新的 \(w_{u'}=w_u\) 之前 \(cls_{w_u}=u\),于是记录往下跳之前的 \(cls_{w_u}\) 然后在搜索时令 \(cls_{w_u}^{'}=u\) 即可。所以 \(X_u=cls_{w_u+1}\)。
之后就可以类似倍增处理,令 \(X^{'}_{u,i}\) 表示 \(u\) 的祖先中点权为 \(w_u+2^i\) 且深度最大的点。所以可以在满足 \(d_{X^{'}_{u,i}}>d_{lca(s,t)}\) 之前不断 \(u\leftarrow X^{'}_{u,i}\)(其中 \(d_u\) 指 \(u\) 的深度),并且之所以是 \(>\) 是因为我在这里处理默认是不处理 \(lca(s,t)\) 的,留给 \(lca(s,t)\rightarrow t\)。那么用 \(\log n\) 的时间完成了这一部分。
接下来开始处理 \(lca(s,t)\rightarrow t\)。发现无法直接像上部分那样直接同理往下跳,因为不知道跳到哪里。然后看看能不能直接从 \(n\) 开始像上一题一样跳 \(n-1,n-2,\cdots\),发现也不行。不妨设对于一个询问 \(i\) 我们 \(s\rightarrow lca(s,t)\) 跳到了 \(A_i\)。区别就在于上方做法我们只能这么跳且贪心最优,可是如果从 \(n\) 开始到根一定最优的,可是如果是到 \(lca(s,t)\) 就可能会出现绝大部分链都在 \(lca(s,t)\) 的上方,所以在下方也连不上 \(A_i\),如:
如果我们从 \(7\) 开始跳的话最终路径拼接是 \(1\rightarrow 2\rightarrow 6\rightarrow 7\) 发现根本不合法长度只有 \(2\),不如 \(1\rightarrow 2\rightarrow 3\rightarrow 4\)。
但是观察到选择的权值是有单调性的。
如果选择的 \(x\) 不存在,那么所有大于 \(x\) 的想要接上去一定要从 \(x+1\rightarrow x\),又因为不存在所以无法成功拼接。
如果存在 \(x\),那么 \(x-1\) 也必须存在。所以一定是有一个 \(x\) 使得所有小于 \(x\) 的都存在且 \(x+1\) 不存在。
所以我们可以考虑二分,二分出一个 \(x\)。判断从最下方的 \(w_u=x\) 开始往上跳,跳到一个最接近 \(lca(s,t)\) 的 \(w_{u^{'}}=x-k\)。这时候我们判断 \(x-k\leq A_i+1\) 的话就说明可以拼接上,则往大考虑,否则往小考虑。对于跳的部分就还是可以类比上方做法只不过 \(X_u=cls_{w_u-1}\),\(X^{'}_{u,i}\) 表示 \(u\) 的祖先中点权为 \(w_u-2^i\) 且深度最大的点。
现在问题就变成了如何快速找到最下方满足 \(w_u=x\) 的点,即维护所有点的所有的 \(cls\)。如果比较一眼的话似乎可以树链剖分+主席树之类的做一做,可是会再多一些 \(\log\),看起来不是很好接受。但是发现如果对于一个点我们可以求出 \(cls\),于是考虑离线。对于一个点 \(u\),我们可以直接访问所有形如 \((s,t=u)\) 的询问,这样的 \(cls\) 就是确定的。可以再去二分,这样就不用一直维护 \(cls\) 了,否则空间会爆炸。
那么我们就可以在 \(O(Q\log m\log n)\) 的时间内解决问题。
代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i,a,b) for(ll i=(a);i<=(b);++i)
using namespace std;
typedef long long ll;
const ll MAXN=2e5+5;
const ll MAXC=5e4+5;
ll n,m,c;
ll p[MAXN],w[MAXN],Id[MAXN];
void init_w(){
rep(i,1,c){
Id[p[i]]=i;
p[i]=i;
};
rep(i,1,n){
w[i]=Id[w[i]];
};
}
vector<ll>adj[MAXN];
ll Q;
struct Query{
ll s,t,md;
}q[MAXN];
ll fa[MAXN][22],dep[MAXN];
void infd(ll u,ll f){
fa[u][0]=f;
dep[u]=dep[f]+1;
rep(i,1,20){
fa[u][i]=fa[fa[u][i-1]][i-1];
};
for(auto v:adj[u]){
if(v==f){
continue;
}
infd(v,u);
}
}
ll lca(ll u,ll v){
if(dep[u]>dep[v]){
swap(u,v);
}
ll cha=dep[v]-dep[u];
for(int i=0;cha;++i){
if(cha&1){
v=fa[v][i];
}
cha>>=1;
}
if(u==v){
return u;
}
for(int i=20;i>=0;--i){
if(fa[u][i]!=fa[v][i]){
u=fa[u][i];
v=fa[v][i];
}
}
return fa[u][0];
}
namespace Zj{
ll cls[MAXN],nxt[MAXN][22],cls1[MAXN];
void dfsz(ll u){
ll ocls=cls[w[u]];
cls[w[u]]=u;
if(w[u]==1){
cls1[u]=u;
}else{
cls1[u]=cls1[fa[u][0]];
}
nxt[u][0]=cls[w[u]+1];
rep(i,1,20){
nxt[u][i]=nxt[nxt[u][i-1]][i-1];
};
for(auto v:adj[u]){
if(v==fa[u][0]){
continue;
}
dfsz(v);
}
cls[w[u]]=ocls;
};
ll ans[MAXN];
ll qryz(ll s,ll t){
s=cls1[s];
if(s==0||dep[s]<=dep[t]){
return 0;
}
for(int i=20;i>=0;--i){
if(dep[nxt[s][i]]>dep[t]){
s=nxt[s][i];
}
}
return w[s];
};
void dfsf(ll u){
ll ocls=cls[w[u]];
cls[w[u]]=u;
nxt[u][0]=cls[w[u]-1];
rep(i,1,20){
nxt[u][i]=nxt[nxt[u][i-1]][i-1];
};
for(auto v:adj[u]){
if(v==fa[u][0]){
continue;
}
dfsf(v);
}
cls[w[u]]=ocls;
};
vector<ll>qy[MAXN];
ll check(ll id,ll s,ll T,ll v){
ll L=v+1,R=m,fans=v;
while(L<=R){
ll mid=(L+R)/2;
ll st=cls[mid];
for(int i=20;i>=0;--i){
if(dep[nxt[st][i]]>=dep[T]){
st=nxt[st][i];
}
}
if(st==0||dep[st]<dep[T]||w[st]>v+1){
R=mid-1;
}else{
L=mid+1;
fans=mid;
}
}
return fans;
};
void qryf(ll u){
ll ocls=cls[w[u]];
cls[w[u]]=u;
for(auto id:qy[u]){
ll md=q[id].md;
ans[id]=check(id,u,md,ans[id]);
}
for(auto v:adj[u]){
if(v==fa[u][0]){
continue;
}
qryf(v);
}
cls[w[u]]=ocls;
};
void Do(){
dfsz(1);
rep(i,1,Q){
ll s=q[i].s,md=q[i].md;
ans[i]=qryz(s,md);
};
memset(nxt,0,sizeof(cls));
dfsf(1);
rep(i,1,Q){
ll t=q[i].t;
qy[t].push_back(i);
};
qryf(1);
rep(i,1,Q){
cout<<ans[i]<<endl;
};
};
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m>>c;
rep(i,1,c){
cin>>p[i];
};
rep(i,1,n){
cin>>w[i];
};
init_w();
rep(i,1,n-1){
ll u,v;
cin>>u>>v;
adj[u].push_back(v);
adj[v].push_back(u);
};
infd(1,0);
cin>>Q;
rep(i,1,Q){
cin>>q[i].s>>q[i].t;
q[i].md=lca(q[i].s,q[i].t);
};
Zj::Do();
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战