题解 【[JRKSJ R6] INFiNiTE ENERZY -Overdoze-】
题意
给你一颗 $n$ 个结点的树,有 $q$ 次询问,每次给出 $x,k$,求任意一个与 $x$ 距离为 $k$ 的点,或者返回无解。
$1\le n,q\le2\times 10^6$。
思路
真的想不到撞了题。$\text{Link}$。
先定 $1$ 为根,求出每个结点深度 $d_i$,子树里深度的最大值 $f_i$ 和取到最大值的结点 $g_i$。
对于一次询问分一下类:
- $d_x\ge k$,直接输出 $k$ 级祖先即可。
- $f_x-d_x\ge k$,此时要输出一个 $x$ 结点的 $k$ 级儿子。我们可以知道,最深的链上一定有这种结点,于是转化为 $g_x$ 的 $f_x-d_x-k$ 级祖先。
- 其他情况。
考虑这一类,$x$ 与答案 $y$ 的路径必定由 $x$ 到 $x$ 的一个祖先 $z$,再到 $z$ 的其它子树中的 $y$。
我们考虑找到最可能成为答案转折点的 $z$ 结点,此时需要 $x$ 到 $z$ 其它子树中的深度最大的结点的距离最大。
给每个结点的子树标号,设为 $1,\dots,k$,则令 $\displaystyle f'_{i,j}$ 为除了 $i$ 结点的第 $j$ 个子树,子树中其它部分的深度最大值,$g'_{i,j}$ 为对应的结点。预处理每个结点的前后缀算出。
令 $x$ 为 $z$ 的第 $j$ 个子树,则 $x$ 到 $z$ 其它子树中的深度最大的结点的距离为 $d_x+f'_{z,j}-2d_z$。考虑对每个 $x$ 预处理出最优的 $z_x$。这个可以开始的时候 $\text{dfs}$ 一遍求出。
找到 $z$ 之后,我们还要求出它除了子树 $j$ 以外的一个 $k-d_x+d_z$ 级儿子,也转化为 $g'_{z,j}$ 的 $f'_{z,j}-k+d_x-2d_z$ 级祖先。注意判断无解。
关于线性求出 $k$ 级祖先:考虑 $\text{dfs}$ 过程中用栈记录所有的祖先,在找到询问时标答案为倒数第 $k$ 位即可。
时间复杂度 $\Theta(n+q)$。
带上小常数的 $\log$ 应该都放过了吧?
std:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff
}
const int N=2e6+10;
struct Edge{
int to,nxt;
}a[N<<1];
int n,q,cnt,head[N];
inline void add(int u,int v){
cnt++;
a[cnt]={v,head[u]};
head[u]=cnt;
}
int d[N],f[N],g[N],*fp[N],*gp[N],son[N],pool[N<<2],*now=pool;
int pf[N],pg[N],sf[N],sg[N],stc[N];
inline void dfs1(int x,int fa){
d[x]=d[fa]+1,f[x]=d[x],g[x]=x;
for(int i=head[x];i;i=a[i].nxt){
int t=a[i].to;
if(t==fa) continue;
dfs1(t,x);
son[x]++;
if(f[t]>f[x]) f[x]=f[t],g[x]=g[t];
}
}
int y[N],z[N];
inline void dfs2(int x,int fa,int py,int pz,int mx){
int sz=son[x],cnt=0;
fp[x]=now,now+=sz+1;
gp[x]=now,now+=sz+1;
for(int i=head[x];i;i=a[i].nxt){
int t=a[i].to;
if(t==fa) continue;
stc[++cnt]=t;
}
assert(cnt==sz);
pf[0]=sf[sz+1]=0;
for(int i=1;i<=sz;i++)
pf[i]=sf[i]=f[stc[i]];
for(int i=1;i<=sz;i++){
if(pf[i]<=pf[i-1])
pf[i]=pf[i-1],pg[i]=pg[i-1];
else
pg[i]=g[stc[i]];
}
for(int i=sz;i>=1;i--){
if(sf[i]<=sf[i+1])
sf[i]=sf[i+1],sg[i]=sg[i+1];
else
sg[i]=g[stc[i]];
}
for(int i=1;i<=sz;i++){
if(pf[i-1]<=sf[i+1])
fp[x][i]=sf[i+1],gp[x][i]=sg[i+1];
else
fp[x][i]=pf[i-1],gp[x][i]=pg[i-1];
}
if(sz==1){
fp[x][1]=d[x],gp[x][1]=x;
}
y[x]=py,z[x]=pz,cnt=0;
for(int i=head[x];i;i=a[i].nxt){
int t=a[i].to;
if(t==fa) continue;
cnt++;
if(fp[x][cnt]-2*d[x]>mx)
dfs2(t,x,gp[x][cnt],x,fp[x][cnt]-2*d[x]);
else
dfs2(t,x,py,pz,mx);
}
}
int ans[N],stk[N],top,cnt2,head2[N],nxt[N],ed[N];
struct Query{
int k,id;
}w[N];
inline void add2(int x,Query t){
cnt2++;
if(!head2[x]) head2[x]=cnt2;
nxt[ed[x]]=cnt2;
ed[x]=cnt2,w[cnt2]=t;
}
inline void dfs3(int x,int fa){
stk[++top]=x;
for(int i=head2[x];i;i=nxt[i]){
Query tmp=w[i];
if(top<=tmp.k) ans[tmp.id]=-1;
else ans[tmp.id]=stk[top-tmp.k];
}
for(int i=head[x];i;i=a[i].nxt){
int t=a[i].to;
if(t==fa) continue;
dfs3(t,x);
}
top--;
}
int main(){
n=read(),q=read();
for(int i=1;i<n;i++){
int u=read(),v=read();
add(u,v),add(v,u);
}
dfs1(1,0);
dfs2(1,0,1,1,-1e9);
for(int i=1;i<=q;i++){
int x=read(),k=read();
if(d[x]>k) add2(x,{k,i});
else if(f[x]-d[x]>=k) add2(g[x],{f[x]-d[x]-k,i});
else{
int py=y[x],pz=z[x];
if(d[x]+d[py]-2*d[pz]<k) ans[i]=-1;
else add2(py,{d[py]-k+d[x]-2*d[pz],i});
}
}
dfs3(1,0);
for(int i=1;i<=q;i++)
write(ans[i]),putc('\n');
flush();
}
好的,被验题人薄纱了。
有一个结论:树上距离点 $x$ 最远的点一定是直径两端点之一,所以把直径提出来讨论一下就好了。
时间复杂度 $\Theta(n+q)$。
再见 qwq~

浙公网安备 33010602011771号