洛谷P5903 树上k级祖先 长链剖分

本文介绍了树的长链剖分及其重要性质,包括长链长度与子树深度的关系,以及从节点到根的跳跃次数上限。通过k级祖先的定义,提出了一种高效的查询方法,利用跳跃至2^h级祖先并结合长链性质,可以在O(1)时间内完成查询。此外,提供了具体的数据结构和算法实现来辅助查询操作。
摘要由CSDN通过智能技术生成
长链剖分:

与重链剖分类似,重链剖分的关键字是子树规模,长链剖分的关键字是子树深度。

长链剖分有一些重要性质:

性质一: x x x k k k级祖先所在的长链长度必然 ≥ k \geq k k,证明如下:

x x x k k k级祖先为 y y y

x x x y y y在同一条长链上,既然 d i s ( x , y ) = = k dis(x,y)==k dis(x,y)==k,那么长链长度一定 ≥ k \geq k k

若他们不在一条长链上,如图, t t t y y y所在的长链的末端
在这里插入图片描述

因为 d i s ( x , y ) = = k dis(x,y)==k dis(x,y)==k,即 o y + o x = = k oy+ox==k oy+ox==k,如果 o x > o t ox>ot ox>ot,那么 x x x在的链就会是 y y y的重链了,因为 o o o总是选择深度深的儿子作为深儿子,因此,一定有 o x ≤ o t ox\leq ot oxot存在,即 o y + o x ≤ o y + o t oy+ox\leq oy+ot oy+oxoy+ot,即 k ≤ o y + o t ≤ l e n [ y ] k\leq oy+ot \leq len[y] koy+otlen[y]

性质二: x x x沿长链向上跳至根的跳跃次数最大是 O ( n ) O(\sqrt{n}) O(n 级别的,证明如下:

因为每次跳跃 k k k长度后链的长度至少是 ≥ k \geq k k的,因为每次都是跳至顶,链的长度一定递增(可能取等),但链顶点跳至其他链需要多 1 1 1的跳跃,所以每次的跳跃长度严格递增,考虑跳跃次数最差的情况,跳跃长度依次为 1 , 2 , 3.... 1,2,3.... 1,2,3....,求和 t ( t + 1 ) 2 ≤ n \frac{t(t+1)}{2}\leq n 2t(t+1)n

解这个方程就有最大跳跃次数了。

树上 k k k级祖先的应用:

要求 x x x k k k级祖先,先找到满足 2 h ≤ k ≤ 2 h + 1 2^h\leq k \leq 2^{h+1} 2hk2h+1 h h h,然后跳跃至 x x x 2 h 2^h 2h级祖先 y y y,根据性质 1 1 1,此时 l e n [ y ] ≥ 2 h len[y]\geq 2^h len[y]2h,剩余需要跳的步数为 k − 2 h ≤ 2 h k-2^h\leq 2^h k2h2h(因为 h h h满足 k ≤ 2 h + 1 k\leq 2^{h+1} k2h+1),得到 k − 2 h ≤ l e n [ y ] k-2^h\leq len[y] k2hlen[y],又因为 k − 2 h ≥ 0 k-2^h\geq0 k2h0,所以至少是 y y y往上跳,最差情况下最高跳至 t o p [ y ] top[y] top[y] l e n [ y ] len[y] len[y]级祖先,那么只需要维护每根链顶点 p p p [ 1 , l e n [ p ] ] [1,len[p]] [1,len[p]]祖先和儿子,就可以 O ( 1 ) O(1) O(1)查询了

#include<bits/stdc++.h>
#define ll long long
#define ui unsigned int
using namespace std;

ui s;
inline ui get(ui x) 
{
    x ^= x << 13;
    x ^= x >> 17;
    x ^= x << 5;
    return s = x; 
}

struct way
{
    int to,next;
}edge[1000005];
int cnt,head[500005];

void add(int u,int v)
{
    edge[++cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt;
}

int n,q,root,depth[500005],top[500005],len[500005];
int max1[500005],son[500005],f[500005][21];
vector<vector<int>>upp(500005),downn(500005);

void dfs1(int u)
{
    depth[u]=depth[f[u][0]]+1; max1[u]=1; 
    for(int i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1];
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==f[u][0]) continue;
        dfs1(v);
        if(max1[v]>max1[son[u]]) son[u]=v;
    }
    max1[u]+=max1[son[u]];
}

void dfs2(int u,int topf)
{
    top[u]=topf; len[u]=depth[u]+max1[u]-1-depth[top[u]]+1;
    if(son[u]) dfs2(son[u],topf);
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==f[u][0]||v==son[u]) continue;
        dfs2(v,v);
    }
}

int query(int x,int k)
{
    if(!k) return x;
    int base=(int)(log(k)/log(2));
    x=f[x][base]; k-=1<<base;
    k-=depth[x]-depth[top[x]]; x=top[x];
    if(!k) return x;
    else if(k>0) return upp[x][k-1];
    return downn[x][-k-1];
}

int main()
{
    cin>>n>>q>>s; ll tot=0,ans=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&f[i][0]);
        if(!f[i][0]) root=i;
        else
        {
            add(i,f[i][0]); 
            add(f[i][0],i);
        }
    }
    dfs1(root); dfs2(root,root);
    for(int i=1;i<=n;i++)
    {
        // printf("max1[%d]=%d\n",i,len[i]);
        if(i!=top[i]) continue;
        for(int j=1,x=f[i][0];j<=len[i];j++,x=f[x][0]) upp[i].push_back(x);
        for(int j=1,x=son[i];j<=len[i];j++,x=son[x]) downn[i].push_back(x);
    }
    for(int i=1;i<=q;i++)
    {
        int x=(get(s)^ans)%n+1,k=(get(s)^ans)%depth[x];
        ans=query(x,k); tot^=i*ans;
    }
    cout<<tot;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值