写在前面

仅想学树剖LCA的同学其实不必要了解线段树
前置知识:树形结构,链式前向星(熟练),线段树(熟练),DFS序(熟练),LCA(了解定义)

树链剖分(树剖):将树分解为一条条不相交的,从祖先到孙子的链。

第零部分:建树与基本概念

建树:给定n个节点用链式前向星建树,这里不做过多赘述,值得一提的是要深入理解建树加边的过程。

基本概念:
1.重儿子:假设x有n个儿子节点,其中以3儿子节点的为根子树大小最大,3就是x的重儿子
2.轻儿子:除重儿子外的所有儿子均为轻儿子

以下图为例
1的重儿子为3,轻儿子为2
3的重儿子为6,其余的为轻儿子
3.轻边:x与轻儿子相连的边
4.重边:x与重儿子相连的边
5.轻链:均由轻儿子构成的一条链
6.重链:均由重儿子构成的一条链

第一部分:预处理节点信息

我们需要的信息如下
dep[X]:x节点的深度
fa[X]:x节点的父亲节点
son[X]:x节点的重儿子
siz[X]:x节点为根的子树大小

top[X]:x节点所在链的顶点
稍微有些晕?不要紧,接着往下看如何用两个DFS实现
首先第一个DFS我们直接获取dep[X],fa[X],son[X],siz[X]

void DFS1(int now,int fath)//传入当前节点和当前节点父亲节点编号{fa[now]=fath;siz[now]=1;son[now]=0;dep[now]=dep[fath]+1;for(int i=head[now];i;i=edge[i].nex){if(edge[i].to==fath)continue;DFS1(edge[i].to,now);siz[now]+=siz[edge[i].to];if(siz[son[now]]<siz[edge[i].to]) //当now节点的重儿子不在是最大的,时候更新了{son[now]=edge[i].to;}}
}

接下来获取top[X]
我们处理的方式:优先对重儿子处理,重儿子处理结束后再处理轻儿子(新开链)

void DFS2(int now,int topx)//topx,先重儿子再轻儿子
{top[now]=topx;if(son[now]){DFS2(son[now],topx);}elsereturn ;for(int i=head[now];i;i=edge[i].nex){if(edge[i].to!=fa[now]&&edge[i].to!=son[now]){DFS2(edge[i].to,edge[i].to);}}
}

看到这里,我们已经可以求解LCA问题了

实例:树剖LCA

OJ:P3379 【模板】最近公共祖先(LCA)
我们来看看预处理后树剖LCA的代码

int LCA(int x,int y){while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]])swap(x,y);x=fa[top[x]];}return dep[x]<dep[y]?x:y;
}

当我们把每个节点对应top记录好了之后,我们要获取两个节点的LCA完全可以通过让当前节点“跳跃”到当前节点所在链的顶端的父节点来实现(还记得我们剖分的不相交的链吗),每次选取当前两个节点中top的深度较大的来跳转。
AC代码 非压行

#include <bits/stdc++.h>
using namespace std;
struct node
{int nex,to;
};
const int N=5e5+10;
node edge[N<<1];
int dep[N],head[N],tot,fa[N],top[N],son[N],siz[N];
void add(int from,int to)
{edge[++tot].nex=head[from];edge[tot].to=to;head[from]=tot;
}
void DFS1(int now,int fath)
{fa[now]=fath;siz[now]=1;son[now]=0;dep[now]=dep[fath]+1;for(int i=head[now];i;i=edge[i].nex){if(edge[i].to==fath)continue;DFS1(edge[i].to,now);siz[now]+=siz[edge[i].to];if(siz[son[now]]<siz[edge[i].to])son[now]=edge[i].to;}
}
void DFS2(int now,int topx)//topx,先重儿子再轻儿子
{top[now]=topx;if(son[now]){DFS2(son[now],topx);}elsereturn ;for(int i=head[now];i;i=edge[i].nex){if(edge[i].to!=fa[now]&&edge[i].to!=son[now]){DFS2(edge[i].to,edge[i].to);}}
}
int LCA(int x,int y)
{while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]){swap(x,y);}x=fa[top[x]];}return dep[x]<dep[y]?x:y;
}
int main()
{int n,l,r,m,s;cin>>n>>m>>s;for(int i=1;i<=n-1;i++){cin>>l>>r;add(l,r);add(r,l);}DFS1(s,0);DFS2(s,s);for(int i=1;i<=m;i++){int x,y;cin>>x>>y;cout<<LCA(x,y)<<endl;}return 0;
}

OK,学到这里如果要是只想学树剖LCA和普通的树上操作读者可以去找树上差分的文章,如果想要继续学习树剖的线段树维护可以继续阅读~

第二部分:树上操作

欢迎来到树链剖分最后的挑战:线段树优化树上操作
我们先分析一下第一部分,看看为什么要用线段树去优化(记着线段树的擅长区域:区间问题)。

第一部分分析:
首先,我们用DFS1获取了dep[X],fa[X],son[X],siz[X]
接着我们用DFS2获取了top[x]。我们来看看DFS2遍历数的过程:先重儿子,再轻儿子
先重儿子,再轻儿子,总是先遍历重儿子的话,一条重链上的遍历顺序是连续的!既然是连续的,那么一条重链就是一整个区间!

上图!

我们观察1 3 6 10这条链已然在新编节点中是1 2 3 4(区间)

观察以新编号为2的节点作为根的子树,里面是2 3 4 5 6(区间)

所以:一条重链和一个子树都恰好是一个区间

我们再来看一般性的情况:源节点从9到8,可以分为:9到2,2到1,8到3,3到1,四条链来进行操作

(圆圈内对应的原本节点的编号,蓝色数字对应的是DFS2遍历的顺序)

根据刚才的分析
1.我们需要在第一部分的DFS2加料,按照DFS2的DFS序来统计出新的节点编号和对应的新节点的权值
2.我们需要根据新的节点编号和新的权值来构造一颗线段树
3.借助我们存储的新节点编号把题目中给咱们的旧节点翻译成新节点来调用线段树。

我们来给DFS2加点东西

void DFS2(int now,int topx)//topx,先重儿子再轻儿子!!!!
{top[now]=topx;nid[now]=++cnt;//nid is new_idnw[cnt]=w[now];//nw is new_weightif(son[now])DFS2(son[now],topx);elsereturn ;for(int i=head[now];i;i=edge[i].nex)if(edge[i].to!=fa[now]&&edge[i].to!=son[now])DFS2(edge[i].to,edge[i].to);
}

okk!我们记录好了新编节点和新编节点对应的重量了!
接下来,我们生成线段树!

void up(int p)
{tree[p]=tree[ls]+tree[rs];
}
void down(int l,int r,int p)
{tag[ls]+=tag[p];tag[rs]+=tag[p];tree[ls]+=(mid-l+1)*tag[p];tree[ls]%=mod;tree[rs]+=(r-mid)*tag[p];tree[rs]%=mod;tag[p]=0;
}
void bulid(int l,int r,int p)
{if(l==r){tree[p]=nw[l];return ;}bulid(l,mid,ls);bulid(mid+1,r,rs);up(p);
}
void update(int l,int r,int nl,int nr,int p,int k)
{if(l>=nl&&r<=nr){tree[p]+=(r-l+1)*k;tree[p]%=mod;tag[p]+=k;return ;}down(l,r,p);if(mid>=nl){update(l,mid,nl,nr,ls,k);}if(mid<nr){update(mid+1,r,nl,nr,rs,k);}up(p);
}
long long query(int l,int r,int nl,int nr,int p)
{if(l>=nl&&r<=nr){return tree[p]%mod;}long long res=0ll;down(l,r,p);if(mid>=nl){res+=query(l,mid,nl,nr,ls);}if(mid<nr){res+=query(mid+1,r,nl,nr,rs);}return res;
}

最后一步,利用nid数组的翻译来调用线段树

记住这个模板哦!

void upd_Range(int x,int y,int k)
{while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]])swap(x,y);你的操作x=fa[top[x]];}你的操作
}

调用!

long long q_Range(int x,int y)//询问一条路径
{long long ans=0ll;while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]])swap(x,y);ans=(ans+query(1,n,nid[top[x]],nid[x],1))%mod;x=fa[top[x]];}if(dep[x]>dep[y])swap(x,y);ans=(ans+query(1,n,nid[x],nid[y],1))%mod;return ans%mod;
}
long long q_Tree(int x)
{return query(1,n,nid[x],nid[x]+siz[x]-1,1)%mod;
}
void upd_Range(int x,int y,int k)
{k%=mod;while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]])swap(x,y);update(1,n,nid[top[x]],nid[x],1,k);x=fa[top[x]];}if(dep[x]>dep[y])swap(x,y);update(1,n,nid[x],nid[y],1,k);
}
void upd_Tree(int x,int k)
{update(1,n,nid[x],nid[x]+siz[x]-1,1,k);
}

题目实例:P3384 【模板】轻重链剖分/树链剖分

ACcode

#include <bits/stdc++.h>
using namespace std;
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
struct node{int nex,to;
};
const int N=1e5+10;
node edge[N<<1];
int head[N],tot;
void add(int from,int to){edge[++tot].nex=head[from];edge[tot].to=to;head[from]=tot;
}
int w[N],n,m,root,mod;//初始权值,n个节点,m个询问,根节点
int dep[N],fa[N],son[N],siz[N];//第一部分DFS
int top[N],nid[N],oid[N],nw[N],cnt;//第二部分DFSint tree[N<<2],tag[N<<2];//线段树所需void DFS1(int now,int fath){fa[now]=fath;siz[now]=1;son[now]=0;dep[now]=dep[fath]+1;for(int i=head[now];i;i=edge[i].nex){if(edge[i].to==fath)continue;DFS1(edge[i].to,now);siz[now]+=siz[edge[i].to];if(siz[son[now]]<siz[edge[i].to])son[now]=edge[i].to;}
}
void DFS2(int now,int topx){//topx,先重儿子再轻儿子top[now]=topx;nid[now]=++cnt;nw[cnt]=w[now];if(son[now])DFS2(son[now],topx);elsereturn ;for(int i=head[now];i;i=edge[i].nex){if(edge[i].to!=fa[now]&&edge[i].to!=son[now])DFS2(edge[i].to,edge[i].to);}
}
int LCA(int x,int y){while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]])swap(x,y);x=fa[top[x]];}return dep[x]<dep[y]?x:y;
}
//线段树部分
void up(int p){tree[p]=tree[ls]+tree[rs];
}
void down(int l,int r,int p){tag[ls]+=tag[p];tag[rs]+=tag[p];tree[ls]+=(mid-l+1)*tag[p];tree[ls]%=mod;tree[rs]+=(r-mid)*tag[p];tree[rs]%=mod;tag[p]=0;
}
void bulid(int l,int r,int p){if(l==r){tree[p]=nw[l];return ;}bulid(l,mid,ls);bulid(mid+1,r,rs);up(p);
}
void update(int l,int r,int nl,int nr,int p,int k){if(l>=nl&&r<=nr){tree[p]+=(r-l+1)*k;tree[p]%=mod;tag[p]+=k;return ;}down(l,r,p);if(mid>=nl)update(l,mid,nl,nr,ls,k);if(mid<nr)update(mid+1,r,nl,nr,rs,k);up(p);
}
int query(int l,int r,int nl,int nr,int p){if(l>=nl&&r<=nr)return tree[p]%mod;long long res=0ll;down(l,r,p);if(mid>=nl)res+=query(l,mid,nl,nr,ls);if(mid<nr)res+=query(mid+1,r,nl,nr,rs);return res;
}
//线段树完结
//线段树使用部分
int q_Range(int x,int y){long long ans=0ll;while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]])swap(x,y);ans=(ans+query(1,n,nid[top[x]],nid[x],1))%mod;x=fa[top[x]];}if(dep[x]>dep[y])swap(x,y);ans=(ans+query(1,n,nid[x],nid[y],1))%mod;return ans%mod;
}
int q_Tree(int x){return query(1,n,nid[x],nid[x]+siz[x]-1,1)%mod;
}
void upd_Range(int x,int y,int k){k%=mod;while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]])swap(x,y);update(1,n,nid[top[x]],nid[x],1,k);x=fa[top[x]];}if(dep[x]>dep[y])swap(x,y);update(1,n,nid[x],nid[y],1,k);
}
void upd_Tree(int x,int k){update(1,n,nid[x],nid[x]+siz[x]-1,1,k);
}
signed main(){scanf("%d%d%d%d",&n,&m,&root,&mod);for(int i=1;i<=n;i++)scanf("%d",&w[i]);for(int i=1,l,r;i<=n-1;i++){scanf("%d%d",&l,&r);add(l,r);add(r,l);}DFS1(root,0);DFS2(root,root);bulid(1,n,1);for(int i=1;i<=m;i++){int c,x,y,k;scanf("%d",&c);if(c==1){scanf("%d%d%d",&x,&y,&k);upd_Range(x,y,k);}else if(c==2){scanf("%d%d",&x,&y);printf("%d\n",q_Range(x,y)%mod);}else if(c==3){scanf("%d%d",&x,&k);upd_Tree(x,k);}else{scanf("%d",&x);printf("%d\n",q_Tree(x)%mod);}}return 0;
}

树链剖分(轻重链)入门相关推荐

  1. 树链剖分(轻重链剖分)

    树链剖分,是一种将树剖分为链,在链上进行各种操作以达到目的的算法.树链剖分(轻重链剖分)可以解决lca(最近公共祖先),树上操作(对树上两点及经过的路的权值进行求和,修改等操作)等一类操作.对于这些问 ...

  2. 树链剖分——轻重链剖分

    2022年01月27日,第十三天 1. 题目链接:P3384 [模板]轻重链剖分/树链剖分 思路:树链剖分直接搞,时间复杂度为 O(nlog2n)O(nlog^2n)O(nlog2n) ,通过模板题, ...

  3. 树链剖分之重链剖分详解

    树链剖分之重链剖分详解 一些概念 算法讲解 应用 求最近公共祖先 对树上的一条链进行修改和查询 相关练习题 一些概念 在学习重链剖分前,首先要明白以下几个概念: 中二重儿子:就是一个节点的儿子中最&q ...

  4. 对LCA、树上倍增、树链剖分(重链剖分长链剖分)和LCT(Link-Cut Tree)的学习

    LCA what is LCA & what can LCA do LCA(Lowest Common Ancestors),即最近公共祖先 在一棵树上,两个节点的深度最浅的公共祖先就是 L ...

  5. 树链剖分(重链剖分法)

    简单的讲一下重剖. 模板题:ZJOI 2008 树的统计 + USACO 2011 Dec Gold 种草 Chapter Former 声明 在此之前,我们先声明以下定义: 重儿子:一个节点的子树大 ...

  6. 树链剖分(轻重链剖+长链剖)

    Part 0 一堆废话 本来树链剖分我是不打算写帖子的,因为我一道树剖的题都没做. 后面在刷树上启发式合并的题目时刚好遇到某道到现在都没调出来的题目要码树剖,感觉这道题在敲烂警钟提醒我好好学树剖,所以 ...

  7. 树链剖分入门+HYSBZ - 1036树的统计Count

    今天学习了树链剖分,记录一下. [题目背景] HYSBZ - 1036树的统计Count [题目分析] 题目要求求任意结点之间路径的和以及路径上最大的结点,还有可能修改.如果正常做可能会很复杂(我也不 ...

  8. 树链剖分(入门学习)

    学习博客:https://www.cnblogs.com/ivanovcraft/p/9019090.html 先来回顾两个问题: 1,将树从x到y结点最短路径上所有节点的值都加上z 这也是个模板题了 ...

  9. 树链剖分入门——[kuangbin]树链剖分

    树链剖分的本质就是将一棵树拆分成一段一段连续的区间,然后放在一起就可以用一棵单独的线段树处理区间问题,只需要将树上节点和线段树节点的对应关系求好就可以很方便的互相转换,而树上两点之间路径的相关问题就可 ...

最新文章

  1. 北京大兴要打造成未来科技新中心?
  2. .NET弹出对话框小结
  3. 网络与服务器编程框架库 acl_3.0.12 发布
  4. 浅谈点击信号对搜索的影响
  5. Luogu P4205 [NOI2005]智慧珠游戏
  6. UVALive6929 Sums【数学】
  7. 【转载】【凯子哥带你学Framework】Activity界面显示全解析(下)
  8. 前端框架Vue(3)——vue-cli 目录结构
  9. 天秀,Excel居然还可以制作二维码
  10. SOLD格雷母线是什么?
  11. golang对比python
  12. CentOS7 修改Swap大小
  13. c语言数码管显示小数点,8位数码管显示正整数和小数及解决鬼影问题
  14. c语言学生学籍管理修改,C语言课设之学生学籍管理系统.doc
  15. 自动化爬虫selenium基础教程
  16. iphone同步android短信,如何从iPhone导入短信到Android手机?
  17. [数学]三角函数与双曲函数及其导数和不定积分
  18. 【语音识别】作业1:语音特征提取
  19. asp.net mvc + javascript生成下载文件
  20. rd授权管理器不显示服务器,2008 r2 找不到RD授权管理器

热门文章

  1. 拼多多买家如何导出“个人中心”订单信息
  2. 你能帮我写出三层电梯的PLC程序吗
  3. JavaScript字符串拼接
  4. Javascript正则表达式验证账号、手机号、电话和邮箱的合法性
  5. 使命召唤ol codol如何自己选择快速低延时的游戏服务器节点
  6. 新技能 | 使用python代码来高效操作Excel表格 (文末赠书5本)
  7. (完)④、iOS-RAC-在实际开发的使用-以登录注册为例子
  8. C语言超市管理系统设计
  9. 电脑上计算机软件一直自动弹出,WinXP光驱总是自动弹出来怎么办?电脑光驱老是自动弹出解决方法...
  10. Allegro 中测量添加单位