长链剖分学习笔记
作者:互联网
概念
长链剖分属于链剖分的一种
一般讲的树剖指重链剖分,它可以用于维护树上路径的信息
而长链剖分则是用于维护有关深度的信息
实现
长链剖分的剖分方法与树链剖分极其相似,只需要把以子树大小判断重儿子改成以节点深度判断长儿子即可
有啥用?
它有这么些性质
- 长链剖分后,所有节点都仅属于一条链(显然
- 任意节点 \(u\) 的第 \(k\) 级祖先 \(f\) 所在链的长度一定大于 \(k\)(若 \(f,u\) 在一条链上,那么显然链长大于 \(k\) ;若 \(f,u\) 不在一条链上,假设 \(f\) 所在链长度小于 \(k\) ,那么 \(f-u\) 的链长显然更长,\(f,u\) 应在一条链上,矛盾)
- 任意节点到达根节点经过的长链数是 \(\sqrt{n}\) 级的(由上条可得每次跳跃到的新链长度不会小于当前链,最坏的情况,长链长度为 \(1,2,3,…, \sqrt{n}\)单调递增)
应用
P5903 【模板】树上 k 级祖先
用树上倍增解决的话可以 \(O(n \log n)\) 预处理, \(O(\log n)\) 回答。但是不够快。长链剖分可以 \(O(n \log n)\) 预处理, \(O(1)\) 回答
预处理:
- 对树进行长链剖分,记录每条链的顶点和深度
- 树上倍增求出每个点的 \(2^n\) 级祖先
- 对于每条链,如果其长度为 \(len\) ,那么在顶点处记录顶点向上的 \(len\) 个祖先和向下的 \(len\) 个链上的儿子
- 对 \(i \in [1,n]\) 求出在二进制下的最高位 \(h_i\)
询问:利用倍增数组先将 \(x\) 跳到 \(x\) 的 \(2^{h_k}\) 级祖先,设剩下还有 \(k^{\prime}\) 级,显然 \(k^{\prime} < 2^{h_k}\) ,因此此时 \(x\) 所在的长链长度一定 \(\geq 2^{h_k} > k^{\prime}\) ,因此可以先将 \(x\) 跳到 \(x\) 所在链的顶点,若之后剩下的级数为正,则利用向上的数组求出答案,否则利用向下的数组求出答案。
Code:
#include <cstdio>
#include <vector>
typedef long long ll;
typedef unsigned int uint;
using namespace std;
const int N=5e5+7,LOGN=21;
vector<int> edge[N],up[N],down[N];
int dp[N][LOGN]; // dp[i][j]表示i的2^j级祖先
int LOG[N],d[N],dep[N],mxdep[N],son[N],top[N];
ll Ans;
uint s;
int n,q,root;
inline uint get(uint x) {
return x^=x<<13,x^=x>>17,x^=x<<5,s=x;
}
inline void dfs1(int x) {
dep[x]=mxdep[x]=dep[dp[x][0]]+1;
for(int i=0,y;i<edge[x].size();++i) {
y=edge[x][i];
dp[y][0]=x;
for(int j=0;dp[y][j];++j)
dp[y][j+1]=dp[dp[y][j]][j];
dfs1(y);
if(mxdep[y]>mxdep[x])
mxdep[x]=mxdep[y],son[x]=y;
}
}
inline void dfs2(int x,int topf) {
top[x]=topf;
if(x==topf) {
for(int i=0,o=x;i<=mxdep[x]-dep[x];++i)
up[x].push_back(o),o=dp[o][0];
for(int i=0,o=x;i<=mxdep[x]-dep[x];++i)
down[x].push_back(o),o=son[o];
}
if(son[x])
dfs2(son[x],topf);
for(int i=0,y;i<edge[x].size();++i) {
y=edge[x][i];
if(y!=son[x])
dfs2(y,y);
}
}
inline int query(int x,int k) {
if(!k)
return x;
x=dp[x][LOG[k]]; // 将x跳到2^h[k]级祖先
k-=1<<LOG[k],k-=dep[x]-dep[top[x]],x=top[x]; // 跳到所在链顶点
return k>=0 ? up[x][k] : down[x][-k]; // 利用向上/向下数组求出答案
}
signed main() {
scanf("%d%d%d",&n,&q,&s);
LOG[0]=-1;
for(int i=1;i<=n;++i)
LOG[i]=LOG[i>>1]+1;
for(int i=1;i<=n;++i) {
scanf("%d",&dp[i][0]);
edge[dp[i][0]].push_back(i);
}
root=edge[0][0];
dfs1(root),dfs2(root,root);
for(int i=1,x,k,lstans=0;i<=q;++i) {
x=(get(s)^lstans)%n+1;
k=(get(s)^lstans)%dep[x];
lstans=query(x,k);
Ans^=1ll*i*lstans;
}
printf("%lld",Ans);
return 0;
}
标签:长链,剖分,int,笔记,祖先,uint,节点 来源: https://www.cnblogs.com/wshcl/p/clpf.html