1 条题解

  • 0
    @ 2025-8-24 22:33:28

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar lgswdn_SA
    舞台少女,每日进化中

    搬运于2025-08-24 22:33:28,当前版本为作者最后更新于2023-02-01 16:11:56,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    不需要任何 log 数据结构的做法。

    首先考虑一个暴力结论:我们走有限次之后(实际上是 O(n2)O(n^2),极限情况是都指向子节点的链),一定会达成以下的局面:所有节点(除了跟节点)都指向父亲。然后就会根据欧拉游走序进行循环了。所以现在的重点是要处理出在循环之前的情况。

    注意,在这个欧拉游走序中,我们每个节点的儿子顺序,应该是在原出边顺序中,从父节点之后的那个儿子开始,父节点之前的那个儿子结束,形成的一个顺序。

    我们把其最终结果的欧拉游走序给求出来。如果我们在某一时刻”解决“了一个树上节点,那么在之后的时刻,遇到这个点就直接按照欧拉游走序上的往后走就行了。所以对于欧拉序上的每一个位置,我们可以打一个标记。而当我们对于 uu,它走出了自己的子树,那么此时它一定会指向自己的父节点,于是自己就被解决了,把自己在欧拉序上出现的位置的标记全部删掉。

    我们假设我们现在在欧拉序的没有标记的 xx 上,那么我们会一路走到第一个有标记的位置 yy。此时 yy 一定还没有被经过(因为如果一个点有标记那么子树内的点也必然有标记,所以 xxyy 子树外,所以如果以前经过过 yy 那么就一定走出过 yy 的子树,与其未被解决矛盾)。所以此时我们就可以跳到 yy 的在原树上的第二个出边处,然后继续往下走(因为会直接转到第二个孩子)。其实在此时,我们就已经可以把 yy 的节点解决了。因为从此之后,它的遍历顺序就和我们最终的欧拉游走序相同了。

    所以我们的操作就是:对于 xx,我们先删除 xx 的所有标记,跳到 xx 在原树上的第二个出边处(即欧拉序上那个点在 xx 之后的最近出现的位置)。然后当 xx 上没有标记(即其原来的第二个儿子为自己的父节点,或者自己是叶节点),就顺序走到下一个标记处。维护每个节点之后的第一个标记,这可以直接上一个并查集。而我们同时也维护时间。走到下一个节点的时间区间 [l,r][l,r] 可能会囊括一些问询,而这段时间内走的是一段连续的欧拉序,我们把这些问询给处理掉即可。

    复杂度正确,因为我们每次遍历到标记节点就会删除一些标记,而两次标记访问之间最多有一次非标记访问。

    要对问询排序所以得勉为其难地带个 log。

    复杂度 O(nα(n)+qlogq)O(n\alpha(n)+q\log q)


    Update:"你可以基排的",所以可以直接做到 O(nα(n)+q)O(n\alpha(n)+q)

    const int N=1.6e6+9;
    
    int n,Q,nxt[N],fa[N],dfn[N],tick,deg[N],tot,hd[N],lst[N],fst[N],ans[N],tag[N];
    pii q[N];
    vi pos[N],t[N];
    struct edge {int to,nxt;} e[N];
    
    int find(int x) {return nxt[x]==x?x:nxt[x]=find(nxt[x]);}
    int dis(int x,int y) {
        if(x<y) return y-x;
        else return tick-x+y;
    }
    void del(int x) {nxt[x]=find(x%tick+1); tag[x]=0;}
    void dfs1(int u) {for(int v:t[u]) if(v!=fa[u]) fa[v]=u, dfs1(v);}
    int calc(int x,int y) {
        x+=y, x%=tick; if(x==0) x=tick;
        return dfn[x];
    }
    void dfs2(int u) {
        dfn[++tick]=u; pos[u].eb(tick); fst[u]=tick;
        for(int i=hd[u];i;i=e[i].nxt) {
            dfs2(e[i].to);
            dfn[++tick]=u; pos[u].eb(tick);
        }
    }
    void work(int p,int time,int qt) {
        int u=dfn[p];
        if(tag[p]) {
            for(int x:pos[u]) del(x);
            while(qt<=Q&&q[qt].fi==time) ans[q[qt].se]=u, qt++;
            if(t[u].size()==1) work(t[u][0],time+1,qt);
            else work(t[u][1],time+1,qt);
        } else {
            int np=find(p);
            if(!tag[np]) {
                while(qt<=Q) ans[q[qt].se]=calc(p,q[qt].fi-time), qt++;
                return;
            }
            int nt=time+dis(p,np);
            while(qt<=Q&&q[qt].fi<nt) ans[q[qt].se]=calc(p,q[qt].fi-time), qt++;
            work(np,nt,qt);
        }
    }
    
    signed main() {
        n=read(), Q=read();
        rep(i,1,n) {
            deg[i]=read();
            rep(j,1,deg[i]) t[i].eb(read());
        }
        dfs1(1);
        rep(i,1,n) {
            int pos=0;
            if(i!=1) {while(t[i][pos]!=fa[i]) pos++;}
            pos=(pos+1)%deg[i], hd[i]=tot+1;
            rep(j,1,deg[i]) {
                if(t[i][pos]==fa[i]) break;
                e[++tot]=(edge){t[i][pos],tot+1};
                pos=(pos+1)%deg[i];
            }
            if(hd[i]>tot) hd[i]=0;
            else e[tot].nxt=0;
        }
        dfs2(1); tick--;
        per(i,tick,1) {
            int u=dfn[i]; lst[u]=i;
            if(fst[u]==i) {
                for(int &x:t[u]) {
                    if(!lst[x]) x=fst[x];
                    else x=lst[x];
                }
            }
        }
        rep(i,1,Q) q[i].fi=read(), q[i].se=i;
        sort(q+1,q+Q+1);
        rep(i,1,tick) nxt[i]=i, tag[i]=1;
        work(1,0,1);
        rep(i,1,Q) printf("%lld\n",ans[i]);
        return 0;
    }
    
    • 1

    信息

    ID
    7156
    时间
    8000ms
    内存
    1024MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者