====== 长链剖分 ====== ===== 算法简介 ===== 一种可以在线性时间复杂度维护子树中**仅限深度有关的信息**的算法,主要用于一些特殊的 $\text{dp}$ ===== 算法思想 ===== 类似重链剖分,将儿子分为重儿子和轻儿子,重儿子组成的链构成长链 但不同的是重链剖分重儿子是子树结点最多的结点,长链剖分长儿子是子树深度最大的结点 长链剖分有两个重要性质 性质一:所有长链长度和为 $n$ 显然所有点均属于且仅属于一条长链,所以性质一成立 性质二:长链剖分的一个重要性质是一个结点 $x$ 的 $k$ 级祖先所在的长链长度一定 $\ge k$ 考虑结点 $x$ 和它的 $k$ 级祖先 $y$,如果 $x$ 和 $y$ 属于同一条长链,该情况下性质二成立 如果 $x$ 和 $y$ 不属于同一条长链,知 $y$ 的重儿子子树的深度一定大于 $x$ 的深度,该情况下性质二也成立 ===== 算法应用 ===== ==== 树上 $k$ 级祖先 ==== [[https://www.luogu.com.cn/problem/P5903|洛谷p5903]] 先一遍 dfs 处理出每个结点的深度、父结点、重儿子,同时用倍增法 $O\left(n\log n\right)$ 时间处理出每个结点的 $2^k$ 级祖先 第二遍 dfs 处理出每个结点所在长链的起点 $x$ 对每个长链的起点 $x$ ,暴力处理出 $x$ 上下 $\text{len}(x)$ 个结点,其中 $\text{len}(x)$ 表示 $x$ 所在长链的长度 由于所有长链长度之和为 $n$ ,所以该暴力处理的时间复杂度为 $O\left(n\right)$ 对每次查询结点 $x$ 的 $k$ 级祖先,记 $h_k=\lfloor \log_2 k\rfloor$,$tk=k-2^{h_k}$ 先从 $x$ 向上跳 $2^{h_k}$ 级到 $y$ , 根据长链剖分性质,知 $y$ 所在长链长度必然 $\ge 2^{h_k}$ 此时还需向上跳 $tk$ 级,$tk\lt 2^{h_k}$,所以 $x$ 的 $k$ 级祖先一定在 $y$ 所在长链起点的上下 $\text{len}(x)$ 级结点范围内 因此在从 $y$ 跳到 $y$ 所在长链起点,最后便可以 $O\left(1\right)$ 访问目标结点 时间复杂度 $O\left(n\log n\right)-O\left(1\right)$ const int MAXN=5e5+5,MAXM=21; unsigned int s; inline unsigned int get(unsigned int x){ x^=x<<13; x^=x>>17; x^=x<<5; return s=x; } struct Edge{ int to,next; }edge[MAXN<<1]; int head[MAXN],m; void Insert(int u,int v){ edge[++m].to=v; edge[m].next=head[u]; head[u]=m; } int d[MAXN],fa[MAXN][MAXM],log_2[MAXN]; int h_son[MAXN],mson[MAXN],p[MAXN]; vector Node_up[MAXN],Node_down[MAXN]; void get_log2(){ log_2[0]=-1; _for(i,1,MAXN) log_2[i]=log_2[i>>1]+1; } void dfs_1(int u,int depth){ mson[u]=d[u]=depth; _rep(i,1,log_2[d[u]]) fa[u][i]=fa[fa[u][i-1]][i-1]; for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; dfs_1(v,depth+1); if(mson[v]>mson[u]){ h_son[u]=v; mson[u]=mson[v]; } } } void dfs_2(int u,int top){ p[u]=top; if(u==top){ for(int i=0,pos=u;i<=mson[u]-d[u];pos=fa[pos][0],i++) Node_up[u].push_back(pos); for(int i=0,pos=u;i<=mson[u]-d[u];pos=h_son[pos],i++) Node_down[u].push_back(pos); } if(h_son[u]) dfs_2(h_son[u],top); for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(v==h_son[u]) continue; dfs_2(v,v); } } int query(int u,int k){ if(!k)return u; u=fa[u][log_2[k]],k-=1<=0?Node_up[u][k]:Node_down[u][-k]; } int main() { int n,q,root,x,y; cin>>n>>q>>s; _rep(i,1,n){ fa[i][0]=read_int(); if(fa[i][0]) Insert(fa[i][0],i); else root=i; } get_log2(); dfs_1(root,0); dfs_2(root,root); long long tot=0,last=0; int u,k; _rep(i,1,q){ u=(get(s)^last)%n+1; k=(get(s)^last)%(d[u]+1); last=query(u,k); tot^=last*i; } enter(tot); return 0; } ==== 合并信息 ==== [[https://www.luogu.com.cn/problem/P5904|洛谷p5904]] 给定一棵 $n$ 个结点的树,问有多少个无序点对 $(i,j,k)$ 满足 $i,j,k$ 两两间距离相等 树形$\text{dp}$,状态转移方程可以参考这份博客[[https://www.luogu.com.cn/blog/xht37/solution-p3565|详情]] 利用指针位移使得父结点以 $O(1)$ 时间继承重儿子信息,暴力继承轻儿子信息 设当前结点为 $u$,$u$ 的儿子结点为 $x$,每次暴力继承时间复杂度为 $O\left(\text{len}\left(x\right)\right)$ 总时间复杂度为 $\sum_u\left(\left(\sum_x \text{len}\left(x\right)\right)-\text{len}\left(u\right)+1\right)$ 每个结点仅在 $\sum_u\left(\left(\sum_x \text{len}\left(x\right)\right)\right)$ 作为它的父结点的子结点出现一次 所以有 $\sum_u\left(\left(\sum_x \text{len}\left(x\right)\right)\right)=\sum_u \text{len}\left(u\right)-\text{len}(root)$ 所以总时间复杂度为 $O\left(n\right)$ const int MAXN=5e5+5; struct Edge{ int to,next; }edge[MAXN<<1]; int head[MAXN],m; void Insert(int u,int v){ edge[++m].to=v; edge[m].next=head[u]; head[u]=m; } int d[MAXN],Son[MAXN],len[MAXN]; LL *f[MAXN],*g[MAXN],dp[MAXN<<2],*pos=dp,ans; void Malloc(int u){ f[u]=pos,pos+=len[u]<<1; g[u]=pos,pos+=len[u]<<1; } void dfs_1(int u,int depth,int fa){ d[u]=depth;Son[u]=0; for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(v==fa) continue; dfs_1(v,depth+1,u); if(len[v]>len[Son[u]]) Son[u]=v; } len[u]=len[Son[u]]+1; } void dfs_2(int u,int fa){ if(Son[u]){ f[Son[u]]=f[u]+1,g[Son[u]]=g[u]-1; dfs_2(Son[u],u); } f[u][0]=1,ans+=g[u][0]; for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(v==fa||v==Son[u]) continue; Malloc(v); dfs_2(v,u); _for(i,0,len[v]){ if(i) ans+=f[u][i-1]*g[v][i]; ans+=g[u][i+1]*f[v][i]; } _for(i,0,len[v]){ g[u][i+1]+=f[u][i+1]*f[v][i]; if(i) g[u][i-1]+=g[v][i]; f[u][i+1]+=f[v][i]; } } } int main() { int n=read_int(),u,v,root=1; _for(i,1,n){ u=read_int();v=read_int(); Insert(u,v); Insert(v,u); } dfs_1(root,0,0); Malloc(root); dfs_2(root,0); enter(ans); return 0; }