题解 【[JRKSJ R6] INFiNiTE ENERZY -Overdoze-】

Link

题意

给你一颗 n 个结点的树,有 q 次询问,每次给出 x,k,求任意一个与 x 距离为 k 的点,或者返回无解。

1n,q2×106

思路

真的想不到撞了题。Link

先定 1 为根,求出每个结点深度 di,子树里深度的最大值 fi 和取到最大值的结点 gi

对于一次询问分一下类:

  1. dxk,直接输出 k 级祖先即可。
  2. fxdxk,此时要输出一个 x 结点的 k 级儿子。我们可以知道,最深的链上一定有这种结点,于是转化为 gxfxdxk 级祖先。
  3. 其他情况。

考虑这一类,x 与答案 y 的路径必定由 xx 的一个祖先 z,再到 z 的其它子树中的 y

我们考虑找到最可能成为答案转折点的 z 结点,此时需要 xz 其它子树中的深度最大的结点的距离最大。

给每个结点的子树标号,设为 1,,k,则令 fi,j 为除了 i 结点的第 j 个子树,子树中其它部分的深度最大值,gi,j 为对应的结点。预处理每个结点的前后缀算出。

xz 的第 j 个子树,则 xz 其它子树中的深度最大的结点的距离为 dx+fz,j2dz。考虑对每个 x 预处理出最优的 zx。这个可以开始的时候 dfs 一遍求出。

找到 z 之后,我们还要求出它除了子树 j 以外的一个 kdx+dz 级儿子,也转化为 gz,jfz,jk+dx2dz 级祖先。注意判断无解。

关于线性求出 k 级祖先:考虑 dfs 过程中用栈记录所有的祖先,在找到询问时标答案为倒数第 k 位即可。

时间复杂度 Θ(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 最远的点一定是直径两端点之一,所以把直径提出来讨论一下就好了。

时间复杂度 Θ(n+q)

再见 qwq~

posted @   ffffyc  阅读(3)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示