长链剖分:
与重链剖分类似,重链剖分的关键字是子树规模,长链剖分的关键字是子树深度。
长链剖分有一些重要性质:
性质一:xxx的kkk级祖先所在的长链长度必然≥k\geq k≥k,证明如下:
设xxx的kkk级祖先为yyy
若xxx与yyy在同一条长链上,既然dis(x,y)==kdis(x,y)==kdis(x,y)==k,那么长链长度一定≥k\geq k≥k
若他们不在一条长链上,如图,ttt为yyy所在的长链的末端

因为dis(x,y)==kdis(x,y)==kdis(x,y)==k,即oy+ox==koy+ox==koy+ox==k,如果ox>otox>otox>ot,那么xxx在的链就会是yyy的重链了,因为ooo总是选择深度深的儿子作为深儿子,因此,一定有ox≤otox\leq otox≤ot存在,即oy+ox≤oy+otoy+ox\leq oy+otoy+ox≤oy+ot,即k≤oy+ot≤len[y]k\leq oy+ot \leq len[y]k≤oy+ot≤len[y]
性质二:xxx沿长链向上跳至根的跳跃次数最大是O(n)O(\sqrt{n})O(n)级别的,证明如下:
因为每次跳跃kkk长度后链的长度至少是≥k\geq k≥k的,因为每次都是跳至顶,链的长度一定递增(可能取等),但链顶点跳至其他链需要多111的跳跃,所以每次的跳跃长度严格递增,考虑跳跃次数最差的情况,跳跃长度依次为1,2,3....1,2,3....1,2,3....,求和t(t+1)2≤n\frac{t(t+1)}{2}\leq n2t(t+1)≤n
解这个方程就有最大跳跃次数了。
树上kkk级祖先的应用:
要求xxx的kkk级祖先,先找到满足2h≤k≤2h+12^h\leq k \leq 2^{h+1}2h≤k≤2h+1的hhh,然后跳跃至xxx的2h2^h2h级祖先yyy,根据性质111,此时len[y]≥2hlen[y]\geq 2^hlen[y]≥2h,剩余需要跳的步数为k−2h≤2hk-2^h\leq 2^hk−2h≤2h(因为hhh满足k≤2h+1k\leq 2^{h+1}k≤2h+1),得到k−2h≤len[y]k-2^h\leq len[y]k−2h≤len[y],又因为k−2h≥0k-2^h\geq0k−2h≥0,所以至少是yyy往上跳,最差情况下最高跳至top[y]top[y]top[y]的len[y]len[y]len[y]级祖先,那么只需要维护每根链顶点ppp的[1,len[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;
}
本文介绍了树的长链剖分及其重要性质,包括长链长度与子树深度的关系,以及从节点到根的跳跃次数上限。通过k级祖先的定义,提出了一种高效的查询方法,利用跳跃至2^h级祖先并结合长链性质,可以在O(1)时间内完成查询。此外,提供了具体的数据结构和算法实现来辅助查询操作。
1万+

被折叠的 条评论
为什么被折叠?



