【算法学习】树链部分
说句闲话
这个东西已经20多天没写了,感觉已经忘没了,非常糟糕,只好赶快补一补,确实还是得多打代码,不然都忘光就丸辣。
前言
树链问题通常是关于树上路径的操作,将路径拆分成一条条链,然后用线段树维护链的权值。
注:本题解并不适用于毫无基础的oier,只是做简单讲解,想了解具体定义请自行查阅。
树链模板题
我们一下都围绕这道题讲述操作,我们需要实现:
-
两点路径权值加;
-
两点路径权值和;
-
节点子树权值加;
-
节点子树权值和;
2次dfs成链
第一次
我们先用第一次 dfs 找到每个节点的以下东西(不懂先放着,继续向下看):
-
节点深度:为了查路径时确定哪个作为起点终点及先动深度更深的点。
-
节点大小:一个子树是一个连续的dfs序,为了操作整颗子树。
-
父节点:当达到链顶时为了转移到另一个链上。
-
重儿子:为了形成重链。
void dfs1(int x,int f,int d){//当前节点,父节点,深度 deep[x]=d;//深度 siz[x]=1;//大小 fa[x]=f;//父节点 int mxson=-1;//重儿子大小 for(int y:v[x]){ if(y==f) continue; dfs1(y,x,d+1); siz[x]+=siz[y]; if(siz[y]>mxson){//比当前已有重儿子更大,更新 son[x]=y; mxson=siz[y]; } } }
第二次
这一次我们要形成树链,优先遍历重链,记录每个点以下内容:
-
dfs序赋新节点编号:为了线段树的节点标号。
-
新节点编号的权值储存:初始值转移过来。
-
记录链顶:为了求路径和,你先别急了解他。
void dfs2(int x,int topf){//当前节点,链顶节点 id[x]=++cnt;//dfs序 w[cnt]=aa[x];//初始化的值转移过来 top[x]=topf;//更新链顶 if(!son[x]){//叶子节点滚! return; } dfs2(son[x],topf);//有点遍历重儿子形成重链 for(int y:v[x]){ if(y!=fa[x]&&y!=son[x]){ dfs2(y,y);//轻儿子链顶是自己 } } }
处理链
子树处理
因为优先遍历重儿子,所有重儿子上 dfs 序是连续,仔细想一下遍历过程就可以发现同一子树上的编号也是连续的,所以子树操作就变成了线段树区间加区间和。
具体如下:(省略了线段树操作)
change(1,1,n,id[x],id[x]+siz[id[x]]-1,k); query(1,1,n,id[x],id[x]+siz[id[x]]-1);
路径处理
这个可以模拟一下,假设我们求 \(4,7\) 的路径上和,他们不在同一条链上,我们找到链顶深度更深的节点操作(7所在的链),那 \(7->6\) 这条路径上的权值直接加和,因为重链 dfs 序连续,所以还是线段树区间加和,此时我们节点要到** \(6\) 的父节点 \(1\) 上**,此时发现 \(1,4\) 在同一条重链上,那 \(1,4\) 的权值直接加和即可,还是线段树区间操作,此时路径操作就呼之欲出了!!!
int sumpath(int x,int y){ int ans=0; while(top[x]!=top[y]){//不在同一条链上 if(deep[top[x]]<deep[top[y]]){//链顶更深!!! swap(x,y);//交换 } ans+=query(1,1,n,id[top[x]],id[y]);//链顶到当前节点 x=fa[top[x]];//到链顶的父节点上 } if(deep[x]>deep[y]){//深度小到大的取值加和 swap(x,y); } ans+=query(1,1,n,id[x],id[y]); return ans; }
树链部分lca
可以发现我们不断地在链上向上跳,这于lca的操作非常相似,所以他是可以求 lca 的,我们不断向上跳到同一条链上,深度更浅的那个点就是两个节点的 lca。
int sumpath(int x,int y){ int ans=0; while(top[x]!=top[y]){ if(deep[top[x]]<deep[top[y]]){ swap(x,y); } x=fa[top[x]]; } if(deep[x]>deep[y]){ return y; } return x; }
边权如何
P4315 月下“毛景树”
如果树的权值到边上成为边权呢,其实还是要转成点权,我们一个点对应一条边,那从根节点出发的边的边权就可以给他的儿子,这样就转成点权了。
当然不可避免的有很多问题,如最后在同一条链上时,要用changesum(1,1,n,id[x]+1,id[y],k);
因为根节点的权值是不算在内的。
单边修改时也要查找边对应的点x=id[e[x*2-1].to]<id[e[x<<1].to]?e[x<<1].to:e[x*2-1].to;
例题
P4427 [BJOI2018] 求和
树上差分的思想,预处理前缀和,从根到该节点k次方的总和,答案需要减去 lca 的路径
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)