看到各位大佬们已经把其他的东西讲的很明白了,我这个 juruo 就讲一讲最基本的树链剖分吧。

0.树剖是什么?能吃吗?

不能吃

树剖是树链剖分的简称,我们一般说的树剖其实指重链剖分。当然,还有一种长链剖分我不会,但是据说不常用。

树链剖分能够把树剖分成许多链,这样就可以用维护区间的方式维护一棵树。

1.怎么剖分

先引入一些概念:

  1. 重儿子:一棵树最大的子树叫重儿子。例如这棵树中3就是1的重儿子:很明显,一棵树的重儿子是唯一的。什么?有多棵子树的大小相同?那就随便选一个呗。
  2. 轻儿子:除了重儿子都是轻儿子。废话
  3. 重边:连接父亲和重儿子的边就是重边。
  4. 轻边:除了重边都是轻边。
  5. 重链:许多重边连起来就叫重链。例如:

这棵树里节点 \(\{1,3,5,6\}\) 可以构成一颗重链。很显然 ,每个重链的起点一定是一个轻儿子。每个节点都属于且仅属于一条重链。<-很重要,一定要记住!

然后就开始剖分了。

具体的剖分过程,就是维护一些数组:

  • \(deep[i]\) 代表节点 \(i\) 的深度。
  • \(top[i]\) 代表节点 \(i\) 所属重链的链顶。(也就是重链里深度最小的那个节点)
  • \(size[i]\) 代表以 \(i\) 为根的子树的大小。
  • \(son[i]\) 代表节点 \(i\) 的唯一一个重儿子是谁。
  • \(f[i]\) 代表节点 \(i\) 的父亲是谁。
  • \(dfn[i]\) 代表节点 \(i\) 的”遍历顺序“。

剖分时要跑两个dfs。经典操作

第一个dfs要维护 \(size\) 、\(son\) 、\(f\)、\(deep\) 这几个数组。

提示:树要用无向图存!

  1. void dfs1(int u,int fa/*记录当前节点父亲是谁*/){
  2. size[u]=1;//因为自己也是子树的一部分
  3. f[u]=fa;
  4. deep[u]=deep[fa]+1;//很明显,当前深度=父亲深度+1
  5. for(int i=0;i<g[u].size();i++){
  6. int v=g[u][i];//遍历每个出边
  7. if(v!=fa){//如果当前出边终点是儿子而不是父亲
  8. dfs1(v,u);//搜
  9. size[u]+=size[v];//加上儿子大小
  10. if(size[v]>size[son[u]]){//找到最大的儿子作为重儿子
  11. son[u]=v;
  12. }
  13. }
  14. }
  15. }

然后我们已经知道了每个节点的重儿子,现在应该把它们连起来成为一条重链了:

  1. void dfs2(int u,int tp/*当前链顶*/){
  2. top[u]=tp;
  3. dfn[u]=++step;
  4. if(son[u]){//如果没有重儿子,那么一个儿子也没有
  5. dfs2(son[u],tp);//优先遍历重儿子,为什么之后再说
  6. for(int i=0;i<g[u].size();i++){
  7. int v=g[u][i];
  8. if(son[u]!=v&&f[u]!=v){//遍历轻儿子
  9. dfs2(v,v);//轻儿子一定是一条重链的链顶
  10. }
  11. }
  12. }
  13. }

如果优先遍历重儿子,那么重链的\(dfn\)一定是连续的。例如:

因为重链的\(dfn\)是连续的,而每个点都属于一条重链,所以可以用线段树维护区间的方式维护点权,这样就不用暴力的一个个查,一个个改了。

一些常见的用法:

  1. query(1,1,n,dfn[top[u]],dfn[u])//查询u到链顶的点权和
  2. modify(1,1,n,dfn[top[u]],dfn[u],3)//把u到链顶的点权都加3

具体到题目上,可以发现甚至连懒惰标记都不需要,没有区间修改的操作。

那么,怎么计算从一个点到另外一个点路径上的点权和?

  1. int query_ans(int u,int v){
  2. int ret=0;
  3. while(top[u]!=top[v]){
  4. if(deep[top[u]]<deep[top[v]]){//注意,一定要比较链顶深度!坑了我好几次
  5. swap(u,v);
  6. }
  7. ret^=query(1,1,n,dfn[top[u]],dfn[u]);//这道题要求异或
  8. u=f[top[u]];
  9. }//就是当uv不在同一条链上时,让链顶深度小的往上跳
  10. if(deep[u]>deep[v]){
  11. swap(u,v);
  12. }
  13. ret^=query(1,1,n,dfn[u],dfn[v]);//当在同一条链上时,把它们之间的点加起来
  14. return ret;
  15. }

知道了这些操作,这题就非常好写了。就是直接把板子套上去嘛!

AC Code:

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define MAXN 200005
  4. int n,q,e[MAXN];
  5. vector<int> g[MAXN];
  6. int dfn[MAXN],step,top[MAXN],size[MAXN],son[MAXN],f[MAXN],deep[MAXN];
  7. void dfs1(int u,int fa){
  8. size[u]=1;
  9. f[u]=fa;
  10. deep[u]=deep[fa]+1;
  11. for(int i=0;i<g[u].size();i++){
  12. int v=g[u][i];
  13. if(v!=fa){
  14. dfs1(v,u);
  15. size[u]+=size[v];
  16. if(size[v]>size[son[u]]){
  17. son[u]=v;
  18. }
  19. }
  20. }
  21. }
  22. void dfs2(int u,int tp){
  23. top[u]=tp;
  24. dfn[u]=++step;
  25. if(son[u]){
  26. dfs2(son[u],tp);
  27. for(int i=0;i<g[u].size();i++){
  28. int v=g[u][i];
  29. if(son[u]!=v&&f[u]!=v){
  30. dfs2(v,v);
  31. }
  32. }
  33. }
  34. }
  35. int tree[MAXN*4];
  36. void push_up(int rt){
  37. tree[rt]=tree[rt*2]^tree[rt*2+1];
  38. }
  39. void modify(int rt,int l,int r,int x,int k){
  40. if(l==r){
  41. tree[rt]=k;
  42. }else{
  43. int mid=(l+r)/2;
  44. if(x<=mid){
  45. modify(rt*2,l,mid,x,k);
  46. }else{
  47. modify(rt*2+1,mid+1,r,x,k);
  48. }
  49. push_up(rt);
  50. }
  51. }
  52. int query(int rt,int l,int r,int L,int R){
  53. if(L>R){return 0;}
  54. if(L<=l&&R>=r){
  55. return tree[rt];
  56. }else{
  57. int mid=(l+r)/2,ret=0;
  58. if(L<=mid){
  59. ret^=query(rt*2,l,mid,L,R);
  60. }
  61. if(R>mid){
  62. ret^=query(rt*2+1,mid+1,r,L,R);
  63. }
  64. return ret;
  65. }
  66. }
  67. int query_ans(int u,int v){
  68. int ret=0;
  69. while(top[u]!=top[v]){
  70. if(deep[top[u]]<deep[top[v]]){
  71. swap(u,v);
  72. }
  73. ret^=query(1,1,n,dfn[top[u]],dfn[u]);
  74. u=f[top[u]];
  75. }
  76. if(deep[u]>deep[v]){
  77. swap(u,v);
  78. }
  79. ret^=query(1,1,n,dfn[u],dfn[v]);
  80. return ret;
  81. }
  82. int main(){
  83. scanf("%d%d",&n,&q);
  84. for(int i=1;i<=n;i++){
  85. scanf("%d",e+i);
  86. }
  87. for(int i=1;i<=n-1;i++){
  88. int u,v,w;
  89. scanf("%d%d",&u,&v);
  90. g[u].push_back(v);
  91. g[v].push_back(u);
  92. }
  93. dfs1(1,0);
  94. dfs2(1,1);
  95. for(int i=1;i<=n;i++){
  96. modify(1,1,n,dfn[i],e[i]);
  97. }
  98. for(int i=1;i<=q;i++){
  99. int op;
  100. scanf("%d",&op);
  101. if(op==1){
  102. int x,k;
  103. scanf("%d%d",&x,&k);
  104. modify(1,1,n,dfn[x],k);
  105. }else{
  106. int u,v;
  107. scanf("%d%d",&u,&v);
  108. printf("%d\n",query_ans(u,v));
  109. }
  110. }
  111. return 0;
  112. }

完结撒花~

树链剖分详解&题解 P6098 【[USACO19FEB]Cow Land G】的更多相关文章

  1. P3384 【模板】树链剖分 题解&&树链剖分详解

    题外话: 一道至今为止做题时间最长的题: begin at 8.30A.M 然后求助_yjk dalao后 最后一次搞取模: awsl. 正解开始: 题目链接. 树链剖分,指的是将一棵树通过两次遍历后 ...

  2. 树链剖分详解(洛谷模板 P3384)

    洛谷·[模板]树链剖分 写在前面 首先,在学树链剖分之前最好先把 LCA.树形DP.DFS序 这三个知识点学了 emm还有必备的 链式前向星.线段树 也要先学了. 如果这三个知识点没掌握好的话,树链剖 ...

  3. 题解 P6098 【[USACO19FEB]Cow Land G】

    震惊,蒟蒻学树剖第二天就打题解 所以说,理解之后树剖这种东西其实难度真心不大.至少这种模板题都可以秒切的 这里推荐一个博客: 树剖详解 蒟蒻就是在这个博客上学到的 如果想看我自己写的总结,请点 我的博 ...

  4. luogu题解P1967货车运输--树链剖分

    题目链接 https://www.luogu.org/problemnew/show/P1967 分析 NOIp的一道裸题,直接在最大生成树上剖分取最小值一下就完事了,非常好写,常数也比较小,然而题解 ...

  5. Qtree3题解(树链剖分(伪)+线段树+set)

    外话:最近洛谷加了好多好题啊...原题入口 这题好像是SPOJ的题,挺不错的.看没有题解还是来一篇... 题意: 很明显吧.. 题解: 我的做法十分的暴力:树链剖分(伪)+线段树+\(set\)... ...

  6. Qtree3题解(树链剖分+线段树+set)

    外话:最近洛谷加了好多好题啊...原题入口 这题好像是SPOJ的题,挺不错的.看没有题解还是来一篇... 题意 很易懂吧.. 题解 我的做法十分的暴力:树链剖分(伪)+线段树+ std :: set ...

  7. luogu题解 P4092 【[HEOI2016/TJOI2016]树】树链剖分

    题目链接: https://www.luogu.org/problemnew/show/P4092 瞎扯--\(O(Q \log^3 N)\)解法 这道先yy出了一个\(O(Q \log^3 N)\) ...

  8. luogu题解P2486[SDOI2011]染色--树链剖分+trick

    题目链接 https://www.luogu.org/problemnew/show/P2486 分析 看上去又是一道强行把序列上问题搬运到树上的裸题,然而分析之后发现并不然... 首先我们考虑如何在 ...

  9. 【BZOJ3307】雨天的尾巴 题解(树链剖分+树上差分)

    题目链接 题目大意:给定一颗含有$n$个结点的树,每次选择两个结点$x$和$y$,对从$x$到$y$的路径上发放一带$z$类型的物品.问完成所有操作后每个结点发放最多的时哪种物品. 普通的树链剖分貌似 ...

随机推荐

  1. js POST调用api接口时,由于OPTIONS请求导致服务器异常

    1.学习心得 当你搜到这个问题时,就表示你已经知道了脚本POST请求接口时,会先执行一次OPTIONS类型的请求.至于为什么会这样,在此就不做描述了,想知道的小伙伴可以查一下:本文主要将我在现实中遇到 ...

  2. Kylin Flink Cube 引擎的前世今生

    Apache Kylin™ 是一个开源的.分布式的分析型数据仓库,提供 Hadoop/Spark 之上的 SQL 查询接口及多维分析(OLAP)能力以支持超大规模数据,它能在亚秒内查询巨大的表. Ky ...

  3. PHP gmstrftime() 函数

    ------------恢复内容开始------------ 实例 根据区域设置格式化 GMT/UTC 日期和时间: <?phpecho(gmstrftime("%B %d %Y, % ...

  4. PHP get_resource_type() 函数

    get_resource_type() 返回资源(resource)类型. 版本要求:PHP 4 >= 4.0.2, PHP 5, PHP 7高佣联盟 www.cgewang.com 语法 st ...

  5. layer.js : n.on is not a function

    当时使用的jQuery为1.4.x的版本.换成高版本就好了. 参考 https://blog.csdn.net/marswill/article/details/69316003

  6. python 创建字典以及操作字典----这是基础知识

    当你编程久了,发现所有的东西都是建立在基础之上的,庞大的代码 你要识别出它的类型是什么 或者返回后类型是什么!? 根据返回的类型 或者需要操作的对象是什么类型  就可以选择相应的方法进行处理 #创建字 ...

  7. MySQL-安装配置篇

    一.MySQL二进制安装包安装 1.环境初始化 1)创建目录mkdir /app/database --安装路径 mkdir /data/3306 --存放数据路径 mkdir /binlog/330 ...

  8. JS中的数组复制问题

    JS中的数组复制问题 前言 首先提到复制,也就是拷贝问题,就必须要明确浅拷贝和深拷贝. 浅拷贝:B由A复制而来,改变B的内容,A也改变 深拷贝:B由A复制而来,改变B的内容,A的内容不会改变 总的来说 ...

  9. Android ExpandListView的用法(补上昨天的)(今天自习)

    今天自习写ExpandListView的作业,昨天没写博客就是去写作业去了. 今天来说昨天内容吧! 其实ExpandListView和ListView的用法大同小异. 首先就是创建一个自己的适配器(现 ...

  10. python分析BOSS直聘的某个招聘岗位数据

    前言 毕业找工作,在职人员换工作,离职人员找工作……不管什么人群,应聘求职,都需要先分析对应的招聘岗位,岗位需求是否和自己匹配,常见的招聘平台有:BOSS直聘.拉钩招聘.智联招聘等,我们通常的方法都是 ...