1可以将树转换成连续的数列,以此来维护树上路径之类信息

2对于每一个节点,将其子节点分为一个重节点和若干个轻节点,重节点为子树最大的节点

3定义一个轻节点一路沿着重子节点走形成的链,其余路径为轻链。

4需要进行两次dfs,第一次获取子树大小和重节点,节点深度,节点的父节点。

5第二次dfs,优先往重子节点走,获取所有点的时间戳,dfs序列,当前节点所在重链上面的端点。

6在同一条重链上的时间戳是连续的,因此就便于维护,比如线段树,莫队之类的。

7对于树的修改或者查询操作,对于两个节点,如果在同一条重链上,那么时间戳是连续的,非常便于维护

8如果不在同一条重链上,不断让两个节点中所在重链端点深度大的往上跳,调到所在重链顶端的父节点上,这个跳跃过程的路径时间戳是连续的,进行信息维护,直到两个点跳到同一条链上,然后进行最后一次维护。

代码实现

按照洛谷的模板题【模板】轻重链剖分/树链剖分 - 洛谷

1数据存储

const int maxn=5e5+10;
vector<int>vv[maxn];
int fa[maxn];//记录父亲节点
int son[maxn];//记录重子节点
int dep[maxn];//记录深度
int dfn[maxn];//记录时间戳
int siz[maxn];//记录子树大小
int val[maxn];//记录节点权值
int a[maxn];//记录节点权值的dfs序
int top[maxn];//记录当前重链的顶部
int cnt;
int mod;
struct node{int l,r,sum,lazy;
}tree[maxn<<2];

2第一次dfs

void dfs1(int u,int fat)
{fa[u]=fat;dep[u]=dep[fat]+1;siz[u]=1;int maxsize=-1;for(int to:vv[u]){if(to==fat)continue;dfs1(to,u);siz[u]+=siz[to];if(siz[to]>maxsize)//如果当前子节点子树大小大于maxsize {maxsize=siz[to];son[u]=to;}}
}

3第二次dfs

void dfs2(int u,int t)
{dfn[u]=++cnt;//记录时间戳 top[u]=t;a[cnt]=val[u];//记录dfs序if(!son[u])//有子节点必有重儿子,所以没有重儿子说明没有子节点return;dfs2(son[u],t);//重儿子和当前节点所处的重链的顶端相同for(int to:vv[u]){if(to==fa[u]||to==son[u])continue;dfs2(to,to);//此时的轻儿子顶端为自己 }
}

3线段树操作

inline void push_up(int p)
{tree[p].sum=tree[p<<1].sum+tree[p<<1|1].sum;tree[p].sum%=mod;
}
void build(int l,int r,int p)
{tree[p].l=l;tree[p].r=r;tree[p].lazy=0;if(l==r){tree[p].sum=a[l];return;}int mid=l+r>>1;build(l,mid,p<<1);build(mid+1,r,p<<1|1);push_up(p);
}
void push_down(int p)
{if(tree[p].lazy){tree[p<<1].sum+=(tree[p<<1].r-tree[p<<1].l+1)*tree[p].lazy;tree[p<<1].sum%=mod;tree[p<<1].lazy+=tree[p].lazy;tree[p<<1].lazy%=mod;tree[p<<1|1].sum+=(tree[p<<1|1].r-tree[p<<1|1].l+1)*tree[p].lazy;tree[p<<1|1].lazy%=mod;tree[p<<1|1].lazy+=tree[p].lazy;tree[p<<1|1].lazy%=mod;tree[p].lazy=0; }
}
void update(int ql,int qr,int v,int p)
{int l=tree[p].l;int r=tree[p].r;if(ql<=l&&r<=qr){tree[p].sum+=(r-l+1)*v;tree[p].sum%=mod;tree[p].lazy+=v;tree[p].lazy%=mod;return; }push_down(p);int mid=l+r>>1;if(mid>=ql)update(ql,qr,v,p<<1);if(mid+1<=qr)update(ql,qr,v,p<<1|1);push_up(p);
}
int query(int ql,int qr,int p)
{int l=tree[p].l;int r=tree[p].r;if(ql<=l&&r<=qr)return tree[p].sum%mod; push_down(p);int mid=l+r>>1;int ans=0;if(mid>=ql)ans+=query(ql,qr,p<<1);ans%=mod;if(mid+1<=qr)ans+=query(ql,qr,p<<1|1);ans%=mod;return ans;
}

4更新节点权值

void upchain(int x,int y,int v)
{while(top[x]!=top[y])//如果 x所处的链的顶端和y所处链的顶端,也就是x和y不属于同一条链上 {if(dep[top[x]]<dep[top[y]])//保证x为所在重链的顶端最深 swap(x,y);update(dfn[top[x]],dfn[x],v,1);//根据x跳跃经过的路径时间戳更新线段树 x=fa[top[x]];//x跳到当前链的订单的父节点 }if(dep[x]<dep[y])swap(x,y);update(dfn[y],dfn[x],v,1);
}

5获取树上信息

int qchain(int x,int y)
{int sum=0;while(top[x]!=top[y])//如果 x所处的链的顶端和y所处链的顶端,也就是x和y不属于同一条链上 {if(dep[top[x]]<dep[top[y]])//保证x为所在重链的顶端最深 swap(x,y);sum+=query(dfn[top[x]],dfn[x],1);//根据x跳跃经过的路径时间戳查询线段树 sum%=mod;x=fa[top[x]];//x跳到当前链的订单的父节点 }if(dep[x]<dep[y])swap(x,y);sum+=query(dfn[y],dfn[x],1);sum%=mod;return sum;
} 

6主函数

int main(){ios::sync_with_stdio(false);cin.tie(0);int n,m,root;cin>>n>>m>>root>>mod;for(int i=1;i<=n;i++)cin>>val[i];for(int i=1;i<n;i++){int u,v;cin>>u>>v;add(u,v);}dfs1(root,0);dfs2(root,root);build(1,cnt,1);for(int i=1;i<=m;i++){// cout<<i<<'\n';int op;cin>>op;if(op==1){int x,y,v;cin>>x>>y>>v;upchain(x,y,v);}else if(op==2){int x,y;cin>>x>>y;cout<<qchain(x,y)<<'\n';}else if(op==3){int x,v;cin>>x>>v;update(dfn[x],dfn[x]+siz[x]-1,v,1);/*dfn[x]+siz[x]-1就是x的子树所有点的时间戳范围*/}else{int x;cin>>x;cout<<query(dfn[x],dfn[x]+siz[x]-1,1)<<'\n';}}return 0;
}

7完整代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+10;
vector<int>vv[maxn];
int fa[maxn];//记录父亲节点
int son[maxn];//记录重子节点
int dep[maxn];//记录深度
int dfn[maxn];//记录时间戳
int siz[maxn];//记录子树大小
int val[maxn];//记录节点权值
int a[maxn];//记录节点权值的dfs序
int top[maxn];//记录当前重链的顶部
int cnt;
int mod;
struct node{int l,r,sum,lazy;
}tree[maxn<<2];
inline void push_up(int p)
{tree[p].sum=tree[p<<1].sum+tree[p<<1|1].sum;tree[p].sum%=mod;
}
void build(int l,int r,int p)
{tree[p].l=l;tree[p].r=r;tree[p].lazy=0;if(l==r){tree[p].sum=a[l];return;}int mid=l+r>>1;build(l,mid,p<<1);build(mid+1,r,p<<1|1);push_up(p);
}
void push_down(int p)
{if(tree[p].lazy){tree[p<<1].sum+=(tree[p<<1].r-tree[p<<1].l+1)*tree[p].lazy;tree[p<<1].sum%=mod;tree[p<<1].lazy+=tree[p].lazy;tree[p<<1].lazy%=mod;tree[p<<1|1].sum+=(tree[p<<1|1].r-tree[p<<1|1].l+1)*tree[p].lazy;tree[p<<1|1].lazy%=mod;tree[p<<1|1].lazy+=tree[p].lazy;tree[p<<1|1].lazy%=mod;tree[p].lazy=0; }
}
void update(int ql,int qr,int v,int p)
{int l=tree[p].l;int r=tree[p].r;if(ql<=l&&r<=qr){tree[p].sum+=(r-l+1)*v;tree[p].sum%=mod;tree[p].lazy+=v;tree[p].lazy%=mod;return; }push_down(p);int mid=l+r>>1;if(mid>=ql)update(ql,qr,v,p<<1);if(mid+1<=qr)update(ql,qr,v,p<<1|1);push_up(p);
}
int query(int ql,int qr,int p)
{int l=tree[p].l;int r=tree[p].r;if(ql<=l&&r<=qr)return tree[p].sum%mod; push_down(p);int mid=l+r>>1;int ans=0;if(mid>=ql)ans+=query(ql,qr,p<<1);ans%=mod;if(mid+1<=qr)ans+=query(ql,qr,p<<1|1);ans%=mod;return ans;
}
inline void add(int u,int v)
{vv[u].push_back(v);vv[v].push_back(u);
}
void dfs1(int u,int fat)
{fa[u]=fat;dep[u]=dep[fat]+1;siz[u]=1;int maxsize=-1;for(int to:vv[u]){if(to==fat)continue;dfs1(to,u);siz[u]+=siz[to];if(siz[to]>maxsize)//如果当前子节点子树大小大于maxsize {maxsize=siz[to];son[u]=to;}}
}
void dfs2(int u,int t)
{dfn[u]=++cnt;//记录时间戳 top[u]=t;a[cnt]=val[u];//记录dfs序if(!son[u])//有子节点必有重儿子,所以没有重儿子说明没有子节点return;dfs2(son[u],t);//重儿子和当前节点所处的重链的顶端相同for(int to:vv[u]){if(to==fa[u]||to==son[u])continue;dfs2(to,to);//此时的轻儿子顶端为自己 }
}
void upchain(int x,int y,int v)
{while(top[x]!=top[y])//如果 x所处的链的顶端和y所处链的顶端,也就是x和y不属于同一条链上 {if(dep[top[x]]<dep[top[y]])//保证x为所在重链的顶端最深 swap(x,y);update(dfn[top[x]],dfn[x],v,1);//根据x跳跃经过的路径时间戳更新线段树 x=fa[top[x]];//x跳到当前链的订单的父节点 }if(dep[x]<dep[y])swap(x,y);update(dfn[y],dfn[x],v,1);
}
int qchain(int x,int y)
{int sum=0;while(top[x]!=top[y])//如果 x所处的链的顶端和y所处链的顶端,也就是x和y不属于同一条链上 {if(dep[top[x]]<dep[top[y]])//保证x为所在重链的顶端最深 swap(x,y);sum+=query(dfn[top[x]],dfn[x],1);//根据x跳跃经过的路径时间戳查询线段树 sum%=mod;x=fa[top[x]];//x跳到当前链的订单的父节点 }if(dep[x]<dep[y])swap(x,y);sum+=query(dfn[y],dfn[x],1);sum%=mod;return sum;
}
int main(){ios::sync_with_stdio(false);cin.tie(0);int n,m,root;cin>>n>>m>>root>>mod;for(int i=1;i<=n;i++)cin>>val[i];for(int i=1;i<n;i++){int u,v;cin>>u>>v;add(u,v);}dfs1(root,0);dfs2(root,root);build(1,cnt,1);for(int i=1;i<=m;i++){//    cout<<i<<'\n';int op;cin>>op;if(op==1){int x,y,v;cin>>x>>y>>v;upchain(x,y,v);}else if(op==2){int x,y;cin>>x>>y;cout<<qchain(x,y)<<'\n';}else if(op==3){int x,v;cin>>x>>v;update(dfn[x],dfn[x]+siz[x]-1,v,1);}else{int x;cin>>x;cout<<query(dfn[x],dfn[x]+siz[x]-1,1)<<'\n';}}return 0;
}

树链剖分--重链剖分相关推荐

  1. HDU 3966 Aragorn's Story (树链点权剖分,成段修改单点查询)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3966 树链剖分的模版,成段更新单点查询.熟悉线段树的成段更新的话就小case啦. 1 //树链剖分 边 ...

  2. 洛谷P4338 [ZJOI2018]历史(LCT,树形DP,树链剖分)

    洛谷题目传送门 ZJOI的考场上最弱外省选手T2 10分成功滚粗...... 首先要想到30分的结论 说实话Day1前几天刚刚刚掉了SDOI2017的树点涂色,考场上也想到了这一点 想到了又有什么用? ...

  3. 树链剖分之长链剖分 详解 题目整理

    树链剖分 题目中出现的树链剖分一般分为两种,重链剖分和长链剖分 重链剖分:选择子树最大的儿子, 将其归入当前点所在 的同一条重链 长链剖分:选择向下能达到的深 度最深的儿子,将其归 入当前点所在的同一 ...

  4. 树链剖分(轻重链)入门

    写在前面 仅想学树剖LCA的同学其实不必要了解线段树 前置知识:树形结构,链式前向星(熟练),线段树(熟练),DFS序(熟练),LCA(了解定义) 树链剖分(树剖):将树分解为一条条不相交的,从祖先到 ...

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

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

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

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

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

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

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

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

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

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

最新文章

  1. 卷积神经网络中的各种池化操作
  2. 【Linux入门到精通系列讲解】shell脚本语法入门教程(看一篇就够了)
  3. javascript取随机数_查缺补漏一些 Javascript 的小技巧笔记
  4. go与JAVA差异_20190312_浅谈gojava差异(二)
  5. 用android LinearLayout和RelativeLayout实现精确布局(转)
  6. HTTP1.0、HTTP1.1和HTTP2.0的区别
  7. 读取记事本内容,自动发布到新浪微博
  8. python输出结果空格分割_用Python编写固定宽度,以空格分隔的CSV输出
  9. LQR轨迹跟踪算法Python算法实现3
  10. 用 Python 轻松搞定 Excel 中的 20 个常用操作
  11. 机器视觉系统工作流程及优势分析
  12. C# 版本设计模式(Design Pattern)(转)
  13. 端口目录Linux操作系统常用命令
  14. 如何将文件快速拷入自己的谷歌云盘
  15. 6. lcd驱动1-硬件原理
  16. linux设备驱动之 i2c设备驱动 at24c08驱动程序分析
  17. 有些CAD通过Arcgis程序读取后,发现面积不对
  18. C语言面试题汇总(持续更)
  19. 机械汽修word模板素材推荐 精品 小众
  20. 能搬砖的游戏有哪些,有哪些比较适合新手去做?

热门文章

  1. 阿里云ACP认证划重点笔记(一)
  2. 2021年煤气找解析及煤气考试技巧
  3. C Primer Plus 第6章_代码和练习题
  4. 有了独自开,我们离自己开发一套系统还会远吗
  5. 返回值的意思,简单理解
  6. 短信怎样写才能吸引客户的眼球
  7. LINUX nautilus 命令
  8. [sdx12] 设置nattype出现(ipa3_wq_handle_rx) from (process_one_work+0x1b0/0x470)导致系统概率死机问题分析及解决方案
  9. **钢琴科技公司--网站改版计划书
  10. 边境牧羊犬AKC标准