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

$\text{Link}$

题意

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

$1\le n,q\le2\times 10^6$。

思路

真的想不到撞了题。$\text{Link}$

先定 $1$ 为根,求出每个结点深度 $d_i$,子树里深度的最大值 $f_i$ 和取到最大值的结点 $g_i$。

对于一次询问分一下类:

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

考虑这一类,$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~

posted @ 2022-05-05 12:56  ffffyc  阅读(16)  评论(0)    收藏  举报  来源