树链剖分

本文章遵守知识共享协议 CC-BY-NC-SA ,转载时须在文章的任一位置附上原文链接和作者署名(rickyxrc)。推荐在我的个人博客阅读。

大意

树链剖分,是一种将一棵树转化为线性的一种算法,可以方便地计算一颗子树的区间和。

前置知识

树链剖分需要两次dfs操作,第一次用于更新深度广度,第二次用于真正的剖分。

第一次搜索代码

void first_dfs(int index,int fath){
deps[index] = deps[fath]+1; // 该点深度等于父亲深度+1
fa[index] = fath; // 设置该点父亲
size[index] = 1; // 设置当前儿子数量
for(auto s:edge[index]){ // 遍历每一条边
if(s == fath)continue; // 不能搜回去
first_dfs(s,index); // 继续向下搜索
size[index] += size[s]; // 更新儿子数量
if(size[hson[index]] < size[s])
hson[index] = s; // 设置重儿子,以方便重链更新。
}
}

第二次搜索代码

void treecut(int index,int topp){
top[index] = topp; // 当前链上最高的点
id[index] = ++liancnt; // 当前点新建一条链
rev[liancnt] = index; // 反向索引更新
if(hson[index] == 0)return; // 如果为叶子节点则返回
treecut(hson[index],topp); // 沿重儿子向下dfs
for(auto s:edge[index]) // 遍历每一条边
if(s!=hson[index] && s!=fa[index]) // 不是重儿子 且 没有往回搜索
treecut(s,s); // 搜索轻儿子
}

此时,树链剖分的结果就会保存在 rev 数组中(将一棵树变为线性的结果)。

树链剖分的另一个作用便是求最近公共祖先(LCA)。

inline int LCA(int a,int b){
while(top[a] != top[b]){ // a与b不在一条重链上
if(deps[top[a]] > deps[top[b]]) // 链顶深度较深的点向上
a = fa[top[a]]; // 注意是 fa[top[a]]
else
b = fa[top[b]];
}
return deps[a]<deps[b] ? a:b; // 深度较浅的就是LCA
}

查询一条链的和

我们需要先写几个辅助函数

只有一条链上的值可以用线段树直接加!

单链加(一条重链上的两个点及之间加)

inline void add(long long x,long long y,long long val){
if(deps[x]>deps[y]) // 判断高低情况
Seg::updateSegment(id[y],id[x],val); // 直接加
else
Seg::updateSegment(id[x],id[y],val);
}

单链查(一条重链上的两个点及之间查询)

inline long long query(long long x,long long y){
if(deps[x]>deps[y]) // 判断高低情况
return Seg::querySegment(id[y],id[x]); // 直接查
else
return Seg::querySegment(id[x],id[y]);
}

简单来说就是在LCA时加链上的内容,所以和LCA的代码没有很大的区别。

代码如下:

inline void chainadd(long long a,long long b,long long val){
while(top[a] != top[b]){
if(deps[top[a]] > deps[top[b]])
add(top[a],a,val), // 这里
a = fa[top[a]];
else
add(top[b],b,val), // 这里
b = fa[top[b]];
}
add(a,b,val); // 这里千万不要忘!
return;
}

更新链

思路相同,不再赘述。

inline long long chainquery(long long a,long long b){
long long ans=0;
while(top[a] != top[b]){
if(deps[top[a]] > deps[top[b]]){
ans += query(top[a],a);
a = fa[top[a]];
}
else{
ans += query(top[b],b);
b = fa[top[b]];
}
}
ans += query(a,b);
return ans;
}

查询子树和

根据上文所提及的特性(树中的任意一颗子树在剖分完成的区间中连续),则可以写出。

可能唯一的问题是定位区间,但是在第一次 dfs 时就已经初始化了每颗子树的 size ,所以可以 O(1) 定位区间。

inline long long queryTree(long long x){
return Seg::querySegment(id[x],id[x] + size[x] - 1);
}
inline long long addTree(long long x,long long y){
Seg::updateSegment(id[x],id[x] + size[x] - 1,y);
}

模板题目及解析

P3379-【模板】最近公共祖先-LCA

这道题一看就是裸的树剖,直接打板子就行。

树链剖分完成之后,剖分完成的树具有一些性质,利用这些性质我们可以进行树上解题。

P3384-【模板】轻重链剖分/树链剖分

剖分完成的树序列的其中一个性质:树中的任意一颗子树在剖分完成的区间中连续

另一个显而易见的性质: 一条链在剖分完成的区间中连续

所以,树链剖分结合线段树,就可以进行树上求和等操作。

线段树的值需要先进行映射,这样才能将树链剖分的优势发挥出来。

for(long long i=1;i<=n;i++)
t_indexed[i] = t[rev[i]]; // 通过rev数组映射
initSegmentTree(1,n,t_indexed);
posted @   rickyxrc  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
点击右上角即可分享
微信分享提示