经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
[C++]P3384 轻重链剖分(树链剖分)
来源:cnblogs  作者:Rosyr  时间:2021/4/6 10:17:58  对本文有异议

[C++]树链剖分

预备知识

  • 树的基础知识
    • 关于这个本文有介绍
  • 邻接表存图
  • 线段树基础
    • 会区间加法和区间结合就可以了P3372
    • 建议阅读这篇Blog
  • 最近公共祖先LCA
    • 虽然用不到这个思想 但是有类似的
    • 有助于快速理解代码
    • 建议阅读这篇Blog

题意解读

题目描述

如题,已知一棵包含 \(N\) 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

操作 1: 格式: \(1\) \(x\) \(y\) \(z\) 表示将树从 \(x\)\(y\) 结点最短路径上所有节点的值都加上 \(z\)

操作 2: 格式: \(2\) \(x\) \(y\) 表示求树从 \(x\)\(y\) 结点最短路径上所有节点的值之和。

操作 3: 格式: \(3\) \(x\) \(z\) 表示将以 \(x\) 为根节点的子树内所有节点值都加上 \(z\)

操作 4: 格式: \(4\) \(x\) 表示求以 \(x\) 为根节点的子树内所有节点值之和

输入格式

第一行包含 44 个正整数 N,M,R,P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。

接下来一行包含 N 个非负整数,分别依次表示各个节点上初始的数值。

接下来 N-1 行每行包含两个整数 x,y,表示点 x 和点 y 之间连有一条边(保证无环且连通)。

接下来 MM 行每行包含若干个正整数,每行表示一个操作,格式如下:

操作 1: x y z;

操作 2: x y;

操作 3: x z;

操作 4: x。

输出格式

输出包含若干行,分别依次表示每个操作 22 或操作 44 所得的结果(对 PP 取模)

选自洛谷

算法思想

树链剖分

顾名思义 就是把树形结构改良成链状结构

这样可以通过线段树方便的维护

为了更好的讲解

这里先列举出几个概念:

  1. 重儿子 是指当前节点的所有儿子中子树最大的儿子
  2. 重链 全部由重儿子组成的链

接下来要进行的第一步

剖分树

剖分树需要有一个标准

这样才可以准确的知道这个树形结构是如何剖分的

这个标准就是 重儿子

这样就能剖出重链

将重链去掉后

再循环这个步骤

就能把一棵树剖成有限个链

举个例子

tushi-1.png
tushi-2.png
tushi-3.png
tushi-4.png

这样树就剖好了

线段树维护

树剖只是把树剖成的链条

但是还没能达到维护数据的目的

这个时候就可以用代码究极繁琐但是实用无比的线段树了

这个时候就需要创造能用线段树维护的条件了

我们首先先要对这棵树的节点按照链来重新排序号

然后再用线段树维护

具体方法详见代码讲解

代码讲解

这里先把变量的含义解释一下:

  1. #define maxn 200007
  2. #define mid ((l+r)>>1)
  3. #define li i<<1
  4. #define ri 1+(i<<1)
  5. int n,m,root,mod;
  6. //n m如题 root为根节点 mod为取余数
  7. int deep[maxn],father[maxn],son[maxn],sub[maxn];
  8. //deep代表深度 father代表父亲节点 son代表重儿子 sub代表子树的大小
  9. int head[maxn],cnt,value[maxn];
  10. //head cnt均为邻接表参数 value代表节点权值
  11. //注 这里value是故意不存在邻接表里的
  12. int top[maxn],id[maxn],value_sort[maxn];
  13. //top 所在链的第一个节点 id新排序后的序号 value_sort新排序后的权值
  14. struct Edge{//邻接表
  15. int u,v;
  16. Edge(int a = 0,int b = 0){
  17. u = head[a];
  18. v = b;
  19. }
  20. }e[maxn << 1];
  21. struct Tree{//线段树
  22. int l,r,sum;
  23. int lazy;
  24. }t[maxn << 1];

这个代码一共有个核心的函数

  • \(Dfs1\)
  • \(Dfs2\)
  • 线段树相关函数
    • \(Build\)
    • \(push\)
    • \(add\)
    • \(search\)
  • \(search\)_\(tree\)
  • \(add\)_\(tree\)

我们依次来看

Dfs1

  1. int dfs1(int u,int fa){
  2. deep[u] = deep[fa] + 1;//u节点的深度比其父亲的深度大1
  3. father[u] = fa;//存下u的父亲为fa
  4. sub[u] = 1;//子树大小 先把自己的1给加上
  5. int maxson = -1;//用来判定重儿子
  6. for(int i = head[u];i;i = e[i].u){
  7. int ev = e[i].v;
  8. if(ev == fa) continue;
  9. sub[u] += dfs1(ev,u);//让子树大小加上儿子节点的子树大小
  10. if(sub[ev] > maxson){//若儿子i的子树大小比以往的都大
  11. maxson = sub[ev];
  12. son[u] = ev;
  13. //那就更新状态
  14. }
  15. }
  16. return sub[u];//返回u的树的大小
  17. }

这是用来求 \(deep\) \(father\) \(son\) \(sub\) 的函数

这部分总体比较简单

注释就直接打代码上了

Dfs2

  1. void dfs2(int u,int topf){
  2. id[u] = ++cnt;
  3. value_sort[cnt] = value[u];
  4. top[u] = topf;
  5. if(!son[u]) return;
  6. dfs2(son[u],topf);
  7. for(int i = head[u];i;i = e[i].u){
  8. int ev = e[i].v;
  9. if(!id[ev])
  10. dfs2(ev,ev);
  11. }
  12. }

这部分是进行剖分

由于重儿子已经得到

那就沿着重儿子进行深搜就行了

注意这里是先进行dfs重儿子的递归再dfs其余儿子

因为我们是要把这条重链先剖出来

这里 \(for\) 循环里 \(if\) 的条件是:

!id[ev]

这说明这个变量还没有被赋值过

即还没有被 \(Dfs\) 到过

这样再进行深搜

线段树相关函数

这部分就是完全用了线段树

不需要改动

唯一注意的就是要加上取模

search_tree

  1. int search_tree(int x,int y){
  2. int ans = 0;
  3. while(top[x] != top[y]){
  4. if(deep[top[x]] < deep[top[y]]) swap(x,y);
  5. ans = (ans + search(1,id[top[x]],id[x])) % mod;
  6. x = father[top[x]];
  7. }
  8. if(deep[x] > deep[y]) swap(x,y);
  9. ans = (ans + search(1,id[x],id[y])) % mod;
  10. return ans;
  11. }

这里的写法有点类似于LCA的树上倍增

来个例子:

tushi-5.png

  • \(top[x] != top[y]\)

其代表了 \(x\)\(y\) 不处于一条链上的时候

就需要先把他们放到一条链上

这里我们对 \(top[x]\)\(top[y]\) 的深度进行比较

\(x\) 处于深层

tushi-6.png

然后再把这个线段的值加上

tushi-7.png

注意这里的 \(top[x]\)\(top[y]\) 并不是同一个点

因为 \(top\) 指的是当前链的顶端

(这里图画的有点小错误)

tushi-8.png

这里重复上述过程

把线段的值加上

tushi-9.png

这样 \(x\) \(y\) 两点间的值就可以得到了

add_tree

  1. void add_tree(int x,int y,int k){
  2. while(top[x] != top[y]){
  3. if(deep[top[x]] < deep[top[y]]) swap(x,y);
  4. add(1,id[top[x]],id[x],k);
  5. x = father[top[x]];
  6. }
  7. if(deep[x] > deep[y]) swap(x,y);
  8. add(1,id[x],id[y],k);
  9. }

这里的思想和 \(search\)_\(tree\) 的思想完全相同

就不再赘述了

吐槽

这个代码真的长

敲起来超费劲

Code

  1. #include<bits/stdc++.h>
  2. #define maxn 200007
  3. #define mid ((l+r)>>1)
  4. #define li i<<1
  5. #define ri 1+(i<<1)
  6. using namespace std;
  7. int n,m,root,mod;
  8. int deep[maxn],father[maxn],son[maxn],sub[maxn];
  9. int head[maxn],cnt,value[maxn];
  10. int top[maxn],id[maxn],value_sort[maxn];
  11. struct Edge{
  12. int u,v;
  13. Edge(int a = 0,int b = 0){
  14. u = head[a];
  15. v = b;
  16. }
  17. }e[maxn << 1];
  18. struct Tree{
  19. int l,r,sum;
  20. int lazy;
  21. }t[maxn << 1];
  22. void Read(){
  23. int a,b;
  24. cin >> n >> m >> root >> mod;
  25. for(int i = 1;i <= n;i++) cin >> value[i];
  26. for(int i = 1;i < n;i++){
  27. cin >> a >> b;
  28. e[++cnt] = Edge(a,b);
  29. head[a] = cnt;
  30. e[++cnt] = Edge(b,a);
  31. head[b] = cnt;
  32. }
  33. }
  34. int dfs1(int u,int fa){
  35. deep[u] = deep[fa] + 1;
  36. father[u] = fa;
  37. sub[u] = 1;
  38. int maxson = -1;
  39. for(int i = head[u];i;i = e[i].u){
  40. int ev = e[i].v;
  41. if(ev == fa) continue;
  42. sub[u] += dfs1(ev,u);
  43. if(sub[ev] > maxson){
  44. maxson = sub[ev];
  45. son[u] = ev;
  46. }
  47. }
  48. return sub[u];
  49. }
  50. void dfs2(int u,int topf){
  51. id[u] = ++cnt;
  52. value_sort[cnt] = value[u];
  53. top[u] = topf;
  54. if(!son[u]) return;
  55. dfs2(son[u],topf);
  56. for(int i = head[u];i;i = e[i].u){
  57. int ev = e[i].v;
  58. if(!id[ev])
  59. dfs2(ev,ev);
  60. }
  61. }
  62. void Build(int i,int l,int r){
  63. t[i].l = l;
  64. t[i].r = r;
  65. if(l == r){
  66. t[i].sum = value_sort[l];
  67. return ;
  68. }
  69. Build(li,l,mid);
  70. Build(ri,mid+1,r);
  71. t[i].sum = t[li].sum + t[ri].sum;
  72. }
  73. void push(int i){
  74. t[li].lazy = (t[li].lazy + t[i].lazy) % mod;
  75. t[ri].lazy = (t[ri].lazy + t[i].lazy) % mod;
  76. int mid_ = (t[i].l + t[i].r) >> 1;
  77. t[li].sum = (t[li].sum + t[i].lazy * (mid_-t[i].l+1)) % mod;
  78. t[ri].sum = (t[ri].sum + t[i].lazy * (t[i].r - mid_)) % mod;
  79. t[i].lazy = 0;
  80. }
  81. void add(int i,int l,int r,int k){
  82. if(l <= t[i].l && t[i].r <= r){
  83. t[i].sum += k * (t[i].r - t[i].l + 1);
  84. t[i].lazy += k;
  85. return ;
  86. }
  87. if(t[i].lazy != 0) push(i);
  88. if(t[li].r >= l)
  89. add(li,l,r,k);
  90. if(t[ri].l <= r)
  91. add(ri,l,r,k);
  92. t[i].sum = (t[li].sum + t[ri].sum) % mod;
  93. }
  94. int search(int i,int l,int r){
  95. if(l <= t[i].l && t[i].r <= r)
  96. return t[i].sum;
  97. push(i);
  98. int ans = 0;
  99. if(t[li].r >= l) ans = (ans + search(li,l,r)) % mod;
  100. if(t[ri].l <= r) ans = (ans + search(ri,l,r)) % mod;
  101. return ans;
  102. }
  103. int search_tree(int x,int y){
  104. int ans = 0;
  105. while(top[x] != top[y]){
  106. if(deep[top[x]] < deep[top[y]]) swap(x,y);
  107. ans = (ans + search(1,id[top[x]],id[x])) % mod;
  108. x = father[top[x]];
  109. }
  110. if(deep[x] > deep[y]) swap(x,y);
  111. ans = (ans + search(1,id[x],id[y])) % mod;
  112. return ans;
  113. }
  114. void add_tree(int x,int y,int k){
  115. while(top[x] != top[y]){
  116. if(deep[top[x]] < deep[top[y]]) swap(x,y);
  117. add(1,id[top[x]],id[x],k);
  118. x = father[top[x]];
  119. }
  120. if(deep[x] > deep[y]) swap(x,y);
  121. add(1,id[x],id[y],k);
  122. }
  123. void interaction(){
  124. int tot;
  125. int x,y,z;
  126. for(int i = 1;i <= m;i++){
  127. cin >> tot;
  128. if(tot == 1){
  129. cin >> x >> y >> z;
  130. add_tree(x,y,z%mod);
  131. }
  132. if(tot == 2){
  133. cin >> x >> y;
  134. cout << search_tree(x,y)%mod << endl;
  135. }
  136. if(tot == 3){
  137. cin >> x >> z;
  138. add(1,id[x],id[x]+sub[x]-1,z%mod);
  139. }
  140. if(tot == 4){
  141. cin >> x;
  142. cout << search(1,id[x],id[x]+sub[x]-1)%mod << endl;
  143. }
  144. }
  145. }
  146. int main(){
  147. Read();
  148. dfs1(root,0);
  149. cnt = 0;
  150. dfs2(root,root);
  151. Build(1,1,n);
  152. interaction();
  153. return 0;
  154. }

原文链接:http://www.cnblogs.com/rosyr050301/p/Tree_chain_partition.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号