#创作计划#浅谈长链剖分
2026-07-21 19:39:55
发布于:浙江
长链剖分
长链剖分是对于每一个点 ,求出它的重子节点 ,满足 的子树中最深的节点深度最大,连接 这条重边。其他边为轻边。若干条首尾相连的重边叫做重链。如果把叶子节点也看做一条重链,那么整棵树就被划分为了若干条重链。
如图(来自 OI-WIKI):

实现过程的伪代码如下:
function dfs1(u,f):
fa[u]=f
dep[u]=dep[f]+1
maxd[u]=dep[u]
hson[u]=0
for v in son(u):
if v!=f:
dfs1(v,u)
if maxd[v]>maxd[hson[u]]:
hson[u]=v
function dfs2(u,t):
top[u]=t
if hson[u]==0:
return
dfs2(hson[u],t)
for v in son(u):
if v!=fa[u] and v!=hson[u]:
dfs2(v,v)
性质
- 从根节点到叶子中,最多有 条轻边。
设从根节点到叶子共有 条轻边,我们从叶子开始向根跳,设 表示当跳了 条轻边到节点 之后, 的值。每沿着轻边向上跳 步,跳到的节点 的子树大小比刚才的节点 的子树大小至少增加了 。也就是 。
,因此 。
不过这个没啥用,因为重链剖分是 的,严格优于长链剖分的 。
- 设 的 级祖先为 ,则 所在重链的长度不低于 。
如果 是重链,那么我们已经知道 的长度等于 。
如果 不是重链,那么假设重链是 ( 为叶子),则该链的长度一定大于 的长度,也就是大于 。
例题1
给定一颗有根树, 次求 的 级祖先。
常用的做法是 的,过不了。
设 为自然数,,我们先求 的 级祖先 ,显然这是可以预处理倍增数组 得到的。接下来,我们需要求 的 级祖先,由于 ,所以 ,即 。设 为这条重链的长度,根据性质 2,,也就是 。我们跳到 所在链的顶端 ,设我们这个操作走了 距离,那么我们还需要向上跳 步。容易知道 ,因此我们在每一个链的顶端 的位置维护两个数组 和 ,分别表示从 向上 步的点和从 沿着重链向下 步的点,其中 。总时间复杂度 。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ui unsigned int
const int MAXN=5e5+55;
struct graph{
int nxt,to;
graph(const int &A=0,const int &B=0):nxt(A),to(B){}
};
int n,q,u,rt,k,ans,tot=1;
ui s;
long long res;
int hd[MAXN],dep[MAXN],maxd[MAXN],hson[MAXN],tp[MAXN],len[MAXN],dist[MAXN],ju[MAXN][20];
vector<int> up[MAXN],down[MAXN];
graph edge[MAXN<<1];
ui get(ui x){
x^=x<<13;
x^=x>>17;
x^=x<<5;
return s=x;
}
void add(const int &u,const int &v){
edge[++tot]=graph(hd[u],v);
hd[u]=tot;
edge[++tot]=graph(hd[v],u);
hd[v]=tot;
return;
}
void dfs1(const int &u,const int &fath){
int v;
ju[u][0]=fath;
maxd[u]=dep[u]=dep[fath]+1;
for(int i=1;i<=19;i++) ju[u][i]=ju[ju[u][i-1]][i-1];
for(int i=hd[u];i;i=edge[i].nxt){
v=edge[i].to;
if(v!=fath){
dfs1(v,u);
maxd[u]=max(maxd[v],maxd[u]);
if(maxd[v]>maxd[hson[u]]) hson[u]=v;
}
}
return;
}
void dfs2(const int &u,const int &top){
int v;
tp[u]=top;
len[top]++;
if(u==top) dist[u]=0;
else dist[u]=dist[ju[u][0]]+1;
if(hson[u]) dfs2(hson[u],top);
for(int i=hd[u];i;i=edge[i].nxt){
v=edge[i].to;
if(v!=ju[u][0]&&v!=hson[u]) dfs2(v,v);
}
if(u==top){
for(int i=0,j=u,k=u;i<=len[u];i++,j=ju[j][0],k=hson[k]){
up[u].push_back(j);
down[u].push_back(k);
}
}
return;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>q>>s;
for(int i=1;i<=n;i++){
cin>>u;
if(u==0) rt=i;
else add(u,i);
}
dfs1(rt,0);
dfs2(rt,rt);
for(int i=1;i<=q;i++){
u=(get(s)^ans)%n+1;
k=(get(s)^ans)%dep[u];
if(k==0){
ans=u;
res^=1ll*i*ans;
continue;
}
u=ju[u][31-__builtin_clz(k)];
k-=1<<31-__builtin_clz(k);
k-=dist[u];
u=tp[u];
if(k>0) ans=up[u][k];
else ans=down[u][-k];
res^=1ll*i*ans;
}
cout<<res;
return 0;
}
例题2
给定一颗根为 的有根树,设 表示在 的子树内到 距离为 的点的数量,对于每一个 ,求使得 最大的 ,如果有多个取最小的 。
不难发现 的计算方法:
设 表示深度,这样做是 的。
考虑长链剖分,设 的重儿子为 ,轻儿子为 。首先,我们利用指针,让 的值直接写到 上,更新答案 ;接着我们处理 的子树,用上面的暴力递推合并答案。
分析复杂度,对于轻儿子 ,我们合并答案的复杂度是 的,也就是 所在重链的长度,而所有重链长度和为 ,因此时间复杂度为 。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+16;
struct graph{
int nxt,to;
graph(const int &A=0,const int &B=0):nxt(A),to(B){}
};
int n,u,v,*p,tot=1;
int hd[MAXN],fa[MAXN],dep[MAXN],maxd[MAXN],hson[MAXN],ans[MAXN],len[MAXN],null[MAXN<<2],*d[MAXN];
graph edge[MAXN<<1];
void add(const int &u,const int &v){
edge[++tot]=graph(hd[u],v);
hd[u]=tot;
edge[++tot]=graph(hd[v],u);
hd[v]=tot;
return;
}
void dfs1(const int &u,const int &fath){
int v;
fa[u]=fath;
maxd[u]=dep[u]=dep[fath]+1;
for(int i=hd[u];i;i=edge[i].nxt){
v=edge[i].to;
if(v!=fath){
dfs1(v,u);
maxd[u]=max(maxd[v],maxd[u]);
if(maxd[v]>maxd[hson[u]]) hson[u]=v;
}
}
len[u]=maxd[u]-dep[u]+1;
return;
}
void dfs2(const int &u){
int v;
if(hson[u]){
d[hson[u]]=d[u]+1;
dfs2(hson[u]);
ans[u]=ans[hson[u]]+1;
}
else{
d[u][0]=1;
ans[u]=0;
return;
}
d[u][0]=1;
if(1>d[u][ans[u]]) ans[u]=0;
else if(1==d[u][ans[u]]&&0<ans[u]) ans[u]=0;
for(int i=hd[u];i;i=edge[i].nxt){
v=edge[i].to;
if(v!=fa[u]&&v!=hson[u]){
d[v]=p;
p+=len[v]<<1;
dfs2(v);
for(int j=1;j<=len[v];j++){
d[u][j]+=d[v][j-1];
if(d[u][j]>d[u][ans[u]]) ans[u]=j;
else if(d[u][j]==d[u][ans[u]]&&j<ans[u]) ans[u]=j;
}
}
}
return;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<n;i++){
cin>>u>>v;
add(u,v);
}
dfs1(1,0);
p=null;
d[1]=p;
p+=len[1]<<1;
dfs2(1);
for(int i=1;i<=n;i++) cout<<ans[i]<<'\n';
return 0;
}
通过本题可以发现,长链剖分可以用于优化复杂度和树高有关的 DP 问题。
例题3
P5904 [POI 2014] HOT-Hotels 加强版
给定一颗树,设 表示 之间的距离,求有多少个无序三元组 满足 。
设 是根, 表示最深深度,考虑朴素 DP。设 表示在 子树内的点 中满足 的点 数量, 表示在 子树内的点 中满足 的无序对 数量。
假设现在搜索到了点 ,正在处理 的儿子 ,可以得到:
边界条件:
这样就是 的复杂度。考虑长链剖分,对于重儿子 ,我们让 直接写到 上, 写到 上;对于轻儿子 ,直接暴力转移。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100005;
struct graph{
int nxt,to;
graph(const int &A=0,const int &B=0):nxt(A),to(B){}
};
int n,u,v,now,tot=1;
long long ans,*p;
int hd[MAXN],dep[MAXN],fa[MAXN],maxd[MAXN],hson[MAXN],len[MAXN];
bool istp[MAXN];
long long null[MAXN<<2],*f[MAXN],*g[MAXN];
graph edge[MAXN<<1];
void add(const int &u,const int &v){
edge[++tot]=graph(hd[u],v);
hd[u]=tot;
edge[++tot]=graph(hd[v],u);
hd[v]=tot;
return;
}
void dfs1(const int &u,const int &fath){
int v;
fa[u]=fath;
maxd[u]=dep[u]=dep[fath]+1;
for(int i=hd[u];i;i=edge[i].nxt){
v=edge[i].to;
if(v!=fath){
dfs1(v,u);
maxd[u]=max(maxd[v],maxd[u]);
if(maxd[v]>maxd[hson[u]]) hson[u]=v;
}
}
len[u]=maxd[u]-dep[u]+1;
return;
}
void dfs2(const int &u){
int v;
if(hson[u]){
f[hson[u]]=f[u]+1;
g[hson[u]]=g[u]-1;
dfs2(hson[u]);
}
f[u][0]=1;
ans+=g[u][0];
for(int i=hd[u];i;i=edge[i].nxt){
v=edge[i].to;
if(v!=fa[u]&&v!=hson[u]){
f[v]=p;
p+=len[v]<<1;
g[v]=p;
p+=len[v]<<1;
dfs2(v);
for(int j=0;j<len[v];j++){
ans+=f[v][j]*g[u][j+1];
if(j) ans+=f[u][j-1]*g[v][j];
}
for(int j=0;j<len[v];j++) g[u][j]+=g[v][j+1];
for(int j=1;j<=len[v];j++) g[u][j]+=f[u][j]*f[v][j-1];
for(int j=1;j<=len[v];j++) f[u][j]+=f[v][j-1];
}
}
return;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<n;i++){
cin>>u>>v;
add(u,v);
}
dfs1(1,0);
p=null;
f[1]=p;
p+=len[1]<<1;
g[1]=p;
p+=len[1]<<1;
dfs2(1);
cout<<ans;
return 0;
}
全部评论 5
这马蜂看着爱上了\se
2026-07-23 来自 广东
0a
2026-07-22 来自 河北
0orz
2026-07-21 来自 上海
0orz
2026-07-21 来自 浙江
0111
2026-07-21 来自 浙江
0





























有帮助,赞一个