洛谷 P5903 【模板】树上 k 级祖先 长链剖分

给定一棵 nn 点有根树。

qq 次查询,第 ii 次查询给 xi,kix_i,k_i,求 xix_ikik_i 级祖先,其答案为 ansians_ians0=0ans_0=0

题目询问在程序中生成,给定一个随机种子 ss 和一个随机函数 get(x)\textrm{get}(x)

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

需要按顺序依次生成询问。

did_iii 的深度,根的深度为 11

对于第 ii 次查询,xi=((get(x)xoransi1)mod  n)+1,ki=(get(s)xoransi1)mod  dxix_i=((\textrm{get}(x)\mathrm{xor}\, ans_{i-1})\mod{n})+1,k_i=(\textrm{get}(s)\textrm{xor}\,ans_{i-1})\mod{d_{x_i}}

长链剖分

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#define MAXN 500010
#define MAXM 1000010
using namespace std;
typedef unsigned int ui;
typedef long long ll;
ll res;
int cnt,n,q,s,g[MAXN],f[MAXN][21],ans,root;
int head[MAXN],d[MAXN],dep[MAXN],son[MAXN],top[MAXN];
vector<int> u[MAXN],v[MAXN];
// 链式前向星 QvQ 
struct Edge{
    int to,nxt; // 每条边包含其指向的结点以及下一条边的编号
}edge[MAXM];
void addedge(int u,int v){
    edge[cnt].to=v; // cnt 表示边的编号,初始化为 0
    edge[cnt].nxt=head[u]; // head[u] 存放的是结点 u 的第一条边的编号
    // head[u] 初始化为 -1 
    // 每当为结点 u 加入一条边时,head[u] 都会指向该边对应的编号
    // 而原来的 head[u] 存放的编号则可以通过当前边对应的编号 edge[cnt].nxt 得到
    // 也就是说 head[u] 存放的边永远是最后加入的
    head[u]=cnt++;
}
inline ui get(ui x){ // 随机数生成器
    return x^=x<<13,x^=x>>17,x^=x<<5,s=x;
}
// 第一个 dfs 主要标记每个结点的深度 d[x]
// 以及每个结点要向哪个孩子延申长链 dep[x] 
// 并且用树上倍增标记各结点的第 2^k 个爸爸 f[x][k] 
void dfs1(int x){ // 长链剖分的第一个 dfs
    // d[x] 表示 x 的深度,首先从 x 为 root 开始
    // f[root][0] = 0 ,此时 d[0] = 0 (默认初始化)
    // 因此 d[root] = 1 which is 符合题意的
    // dep[x] 记录的是以 x 为根节点的子树中最大的深度
    dep[x]=d[x]=d[f[x][0]]+1;
    for(int i=head[x];i!=-1;i=edge[i].nxt){ // 链式前向星风格的遍历结点的所有边
        // head[x] 表示结点 x 的第一条边的编号,edge[i].nxt 表示该边下一条边的编号
        // 这两条边的起点是一样的。直至 edge[i].nxt 为 -1 
        int y=edge[i].to; // x 的儿子!
        f[y][0]=x; // 标记 y 的第 2^0=1 个爸爸是 x 
        // 树上倍增,y 的第 2^{i+1} 个爸爸是 y 的第 2^i 个爸爸的第 2^i 个爸爸
        for(int i=0;f[y][i];i++)f[y][i+1]=f[f[y][i]][i];
        dfs1(y); 
        // son[x] 表示 x 的孩子中拥有最深子树的那一个
        // 以此为基础形成长链
        if(dep[y]>dep[x])dep[x]=dep[y],son[x]=y;
    }
}
void dfs2(int x,int p){
    top[x]=p; // x 所在长链的顶端结点为 top[x]
    if(x==p){
        for(int i=0,k=x;i<=dep[x]-d[x];i++)
            u[x].push_back(k),k=f[k][0]; // u[x][k] 为 x 向上 k 个结点
        for(int i=0,k=x;i<=dep[x]-d[x];i++)
            v[x].push_back(k),k=son[k];  // u[x][k] 为 x 向下 k 个结点
        // 注意此处 x 必定是在长链顶端的
    }
    if(son[x])dfs2(son[x],p); // 顺着长链进行遍历
    for(int i=head[x];i!=-1;i=edge[i].nxt){
        int y=edge[i].to;
        if(y!=son[x])dfs2(y,y); // 从非长链儿子处开始一条长链
    }
}
int ask(int x,int k){
    if(!k)return x;
    // 首先向上移 2 的幂次步,然后移到当前所在长链的顶部 x 
    x=f[x][g[k]],k-=1<<g[k],k-=d[x]-d[top[x]],x=top[x];
    // k 为正,则结果在 x 的上方,否则在下方
    return k>=0?u[x][k]:v[x][-k];
}
int main(){
#ifdef WINE
    freopen("data.in","r",stdin);
#endif
    memset(head,-1,sizeof(head));
    scanf("%d%d%d",&n,&q,&s);
    g[0]=-1;
    for(int i=1;i<=n;i++){
        scanf("%d",&f[i][0]);
        addedge(f[i][0],i);
        // g[1]=0, g[2]=1, g[3]=1, g[4]=2 ...
        // g[k]=log2(k)
        g[i]=g[i>>1]+1;
    }
    root=edge[head[0]].to;
    dfs1(root);dfs2(root,root);
    for(int i=1,x,k;i<=q;i++){
        x=(get(s)^ans)%n+1;
        k=(get(s)^ans)%d[x];
        res^=1ll*i*(ans=ask(x,k));
    }
    printf("%lld\n",res);
    return 0;
}

在这里插入图片描述

posted @ 2020-06-18 00:51  winechord  阅读(88)  评论(0编辑  收藏  举报