P7518 [省选联考 2021 A/B 卷] 宝石

P7518 [省选联考 2021 A/B 卷] 宝石

Solution:

首先我们注意到宝石收集器上的顺序是不变的,所以我们考虑将每个点的点权转化为其在宝石收集器上的排名(后文的权值都是这个意思)

然后我们记录两个数组:\(f_1[u][i]和f_2[u][i]\)用来表示在rt->u这段路径上,权值为\(w[u]+2^i\) or \(w[u]-2^i\)的点最后一次出现的位置.

然后我们来考虑一下这个数组意味着什么:由于我们求\(f_1,f_2\)的方式都是倍增,根据递推式:\(f[u][i]=f[f[u][i-1]][i-1]\)我们不难发现:如果\(f[u][i] \neq 0\)则说明
\(\forall j \in[1,i],f[u][j] \neq 0\)也就是说\([u,u+2^i]\)这段区间上的宝石依次出现在了u->rt这段路上

那我们对于每个查询 (u,v) 将其以lca为中点分为 u->lca,lca->v 的上下两段,那么我们对上行段的查询就很显然了,首先在rt->u这段路上找到最后一个 w[x]=1 的节点x,然后利用倍增不断向上跳,只要满足\(dep[f_1[x'][i]] \ge dep[lca]\),就向上跳,最后跳到的节点mid就是上行段能取到的最右端

然后我们考虑如何处理下行段,我们显然不能直接向上跳,因为答案的右端点不是固定的

但是我们可以二分答案的右端点然后去check是可行的

还记得刚才我们还记录了一个数组 \(f_2[u][i]\),表示\(w[u]-2^i\)的点最后一次出现的位置,所以我们对于一个答案val,在rt->v这段路上找到最后一个 w[y]=val 的节点y,然后跳倍增:
只要满足\(dep[f_2[y'][i]] \ge dep[lca]\) 就不断向上跳,最后我们得到了一个节点reach,

那么显然:[1,mid] && [reach,val]都是可达的,所以只要
\([1,mid] \cup [reach,val] = [1,val]\)那么val就是合法的

然后再来补一个东西:

"rt->v这段路上找到最后一个 w[y]=val 的节点y"
"rt->u这段路上找到最后一个 w[x]=1 的节点x"

这个东西显然就是用主席树来实现:主席树的叶子节点[val,val]维护的就是w=val的节点最后一次出现的位置

话说主席树为什么每次都是干这种不占什么篇幅但是又十分重要而且码量不怎么小的活呢 😦

然后这题就愉快的做完了

Code:

#include<bits/stdc++.h>
const int N=2e5+5;
const int lg=25;
using namespace std;
vector<int> E[N];
int f1[N][lg],f2[N][lg],f[N][lg];
int w[N],rk[N],p[N],st[N];
int dep[N];
int n,m,cnt,tot,c,q;
void dfs1(int u,int fa)
{
f[u][0]=fa;dep[u]=dep[fa]+1;
for(int v : E[u])
{
if(v==fa)continue;
dfs1(v,u);
}
}
void dfs2(int u,int fa)
{
int last=st[w[u]];
st[w[u]]=u;
f1[u][0]=st[w[u]+1],f2[u][0]=st[w[u]-1];
for(int v : E[u])
{
if(v==fa)continue;
dfs2(v,u);
}
st[w[u]]=last;
}
int LCA(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=20;~i;i--){if(dep[f[x][i]]>=dep[y])x=f[x][i];}
if(x==y)return x;
for(int i=20;~i;i--){if(f[x][i]!=f[y][i]){x=f[x][i],y=f[y][i];}}
return f[x][0];
}
//Segment_Tree
struct Tree{
int ls,rs,pos;
}t[N*40];
int rt[N];
void insert(int &x,int y,int l,int r,int val,int pos)
{
t[x=++cnt]=t[y];
if(l==r){t[x].pos=pos;return ;}
int mid=l+r>>1;
if(val<=mid)insert(t[x].ls,t[y].ls,l,mid,val,pos);
if(mid<val) insert(t[x].rs,t[y].rs,mid+1,r,val,pos);
}
int query(int x,int l,int r,int val)
{
if(!x)return 0;
if(l==r)return t[x].pos;
int mid=l+r>>1;
if(val<=mid) return query(t[x].ls,l,mid,val);
return query(t[x].rs,mid+1,r,val);
}
void build(int u,int fa)
{
insert(rt[u],rt[fa],1,n,w[u],u);
for(int v:E[u])
{
if(v==fa)continue;
build(v,u);
}
}
#undef ls
#undef rs
#undef mid
//end
int up(int u,int d)
{
if(dep[u]<d)return 0;
for(int i=20;~i;i--){if(dep[f1[u][i]]>=d)u=f1[u][i];}
return w[u];
}
int down(int u,int d)
{
for(int i=20;~i;i--){if(dep[f2[u][i]]>=d)u=f2[u][i];}
return w[u];
}
bool check(int v,int val,int d,int mid)
{
int y=query(rt[v],1,n,val);
if(dep[y]<d)return 0;
int reach=down(y,d);
return (reach<=(mid+1));
}
void work()
{
cin>>n>>m>>c;
for(int i=1;i<=c;i++){scanf("%d",&p[i]);}
for(int i=1;i<=n;i++){scanf("%d",&w[i]);}
for(int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
E[x].push_back(y);E[y].push_back(x);
}
for(int i=1;i<=c;i++){rk[p[i]]=++tot;}
for(int i=1;i<=n;i++){w[i] = rk[w[i]] ? rk[w[i]] : ++tot;}
dfs1(1,0);
dfs2(1,0);
build(1,0);
for(int i=1;i<=n;i++)for(int j=1;j<=20;j++){f[i][j]=f[f[i][j-1]][j-1];}
for(int i=1;i<=n;i++)for(int j=1;j<=20;j++){f1[i][j]=f1[f1[i][j-1]][j-1];}
for(int i=1;i<=n;i++)for(int j=1;j<=20;j++){f2[i][j]=f2[f2[i][j-1]][j-1];}
cin>>q;
for(int i=1,u,v;i<=q;i++)
{
scanf("%d%d",&u,&v);
int lca=LCA(u,v);
int x=query(rt[u],1,n,1);
int mid=min(up(x,dep[lca]),c);
int l=mid+1,r=c,ans=mid;
while(l<=r)
{
int val=l+r>>1;
if(check(v,val,dep[lca],mid))
{
l=val+1,ans=val;
}
else r=val-1;
}
printf("%d\n",ans);
}
}
int main()
{
//freopen("P7518_3.in","r",stdin);
//freopen("gem.out","w",stdout);
work();
return 0;
}
posted @   liuboom  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示