这是一篇迟来的博客,由于我懒得写文章,本篇以两个问题阐述笔者对树链剖分的初步理解。

Q1:树链剖分解决什么问题?

  树链剖分,就是把一棵树剖分成若干连续的链,将这些链里的数据映射在线性数组上维护。比方说我们想要维护树上任意两点间的lca,或者支持一段路径或一棵子树的修改和查询,都可以用树链剖分来解决。

Q2:树链剖分是什么原理?

  以经典的重链剖分为例,列举笔者认为是树剖核心的两个基本数据结构性质:

  1、树剖序:树剖所维护的几个基本信息如dfn(时间戳)、size(子树大小)、fa(树上父亲),在这里不过多赘述。这里只分析基于son(重儿子)所得出的树剖序的本质。

  我们在打时间戳遍历原树的时候,实际上做了遍特殊的dfs,这个dfs以优先遍历重儿子为原则,对应的dfs序实际上就是树剖序。换言之,树剖序是一种特殊的dfs序,它首先符合dfs序的特征,如“u的子树在树剖序里是一段连续区间”这样的性质支持我们可以用区间数据结构(线段树)来维护子树的信息。

  同时,树剖序有自己的独特性质。相信能看到这篇博客的同学都对所谓“重链”有了了解:每条重链都是由叶子结点沿返祖边指向祖先方向的一条树链,它上面除了最浅的一个点以外的所有结点都是对应祖先的重儿子。两条重链之间通过一条轻边衔接。既然我们优先遍历重儿子,每段重链在树剖序上都是一段连续的区间:而每个节点要么是重儿子(在重链上),要么是某条重链的起点;那么每个节点都一定且仅属于某条重链。所以我们按树剖序得到的线性数组就是由若干条重链拼成的,我们可以在其上用维护区间的方法便可以维护整棵树的信息。

  2、重链的优越性

  那么,树链剖分的高效性何在?首先,我们记录每个点所在重链的链头,从某一点转移到链头的复杂度是O(1)的。修改、查询每段树链用线段树log级维护。而我们需要沿轻边跳跃多少次就可以覆盖整条路径呢?

  引理:任一点到根节点的轻边最多有log条。

  这实际上是个很显然的性质:最坏的情况就是每条边都是轻边,每次从轻儿子v跳到u,由于size[v] < size[son[u]],子树的大小便至少会增加一倍,那么最多会增加log次,这就是我们要按轻重剖分的原因。

  于是树链剖分每次询问都1最坏时间为O(logn*logn),实际上很难有极端数据来卡到这个限度,一般还是很优秀的。(笔者用树链剖分打出的lca板子居然跑得和倍增一样快)

  至此树剖的整体思路已经明了,我们在剖分出的线性区间内架起一棵线段树来维护树上信息即可。下面附上我的树剖代码,码风应该比较清爽,其中线段树的写法也是从大佬那里抄来的精华。希望对各位学习树剖的同学有所帮助。

  1. #include <iostream>
  2. #include <cctype>
  3. #include <cstdio>
  4. #define maxn 100010
  5. #define BUG putchar('*')
  6. using namespace std;
  7. template <typename T>
  8. void read(T &x) {
  9. x = 0;
  10. int f = 1;
  11. char ch = getchar();
  12. while (!isdigit(ch)) {
  13. if (ch == '-')
  14. f = -1;
  15. ch = getchar();
  16. }
  17. while (isdigit(ch)) {
  18. x = x * 10 + (ch ^ 48);
  19. ch = getchar();
  20. }
  21. x *= f;
  22. return;
  23. }
  24. int mod;
  25. int head[maxn], top;
  26. int n, m, root;
  27. int val[maxn], w[maxn];  //共计三个数据数组:原始值、树剖序对应值和线段树
  28. struct E {
  29. int to, nxt;
  30. } edge[maxn << 1];
  31. inline void insert(int u, int v) {
  32. edge[++top] = (E) {v, head[u]};
  33. head[u] = top;
  34. }
  35. namespace segement_tree {   //线段树部分
  36. #define lc (nd<<1)
  37. #define rc ((nd<<1)|1)
  38. struct node {
  39. int val, len;
  40. inline friend node operator + (node a, node b) {
  41. return (node) {(a.val + b.val) % mod, a.len + b.len};
  42. }
  43. } data[maxn << 2];
  44. int tag[maxn << 2];   //lazy_tag只维护简单的加减
  45. inline node operator * (node a, int b) {
  46. return (node) {a.val + (b * a.len) % mod, a.len};
  47. }
  48. inline void put_tag(int nd, int del) {
  49. data[nd] = data[nd] * del;
  50. tag[nd] = tag[nd] + del;
  51. }
  52. inline void update(int nd) {
  53. data[nd] = data[lc] + data[rc];
  54. }
  55. inline void push_down(int nd) {   //下推,同时释放当前节点的tag信息
  56. put_tag(lc, tag[nd]);
  57. put_tag(rc, tag[nd]);
  58. tag[nd] = 0;
  59. }
  60. void build(int nd, int l, int r) {
  61. if (l == r) {
  62. data[nd] = (node) {w[l], 1};
  63. return;
  64. }
  65. int mid = (l + r) >> 1;
  66. build(lc, l, mid);
  67. build(rc, mid + 1, r);
  68. update(nd);
  69. }
  70. void modify(int nd, int l, int r, int ql, int qr, int del) {
  71. if (l >= ql && r <= qr) {
  72. put_tag(nd, del);
  73. return;
  74. } else if (l > qr || r < ql)
  75. return;
  76. push_down(nd);
  77. int mid = (l + r) >> 1;
  78. modify(lc, l, mid, ql ,qr, del);
  79. modify(rc, mid + 1, r, ql, qr, del);
  80. update(nd);
  81. }
  82. int query(int nd, int l, int r, int ql, int qr) {
  83. if (l >= ql && r <= qr) {
  84. return data[nd].val;
  85. } else if (l > qr || r < ql)
  86. return 0;
  87. push_down(nd);
  88. int mid = (l + r) >> 1;
  89. return (query(lc, l, mid, ql, qr) + query(rc, mid + 1, r, ql, qr)) % mod;
  90. }
  91. }
  92. namespace Divtree {
  93. using namespace segement_tree;  //我感觉这个写法挺好的,表明了树剖对线段树的调用关系
  94. int ftop[maxn], f[maxn], size[maxn], son[maxn], d[maxn];
  95. int timer;
  96. int id[maxn];//树剖序
  97. void dfs1(int u, int pre) {   //第一次dfs,维护节点的深度、父亲、重量以及重儿子信息
  98. f[u] = pre;
  99. size[u] = 1;
  100. d[u] = d[pre] + 1;
  101. for (int i = head[u]; i; i = edge[i].nxt) {
  102. int v = edge[i].to;
  103. if (v == pre) continue;
  104. dfs1(v, u);
  105. size[u] += size[v];
  106. if (size[v] > size[son[u]])
  107. son[u] = v;
  108. }
  109. }
  110. void dfs2(int u, int tp) {    //按树剖序遍历,把树上信息剖分在线性数组上
  111. ftop[u] = tp;
  112. id[u] = ++timer;
  113. w[timer] = val[u];   //拷贝
  114. if (son[u])
  115. dfs2(son[u], tp);  //搭建当前重链
  116. for (int i = head[u]; i; i = edge[i].nxt) {
  117. int v = edge[i].to;
  118. if (v != f[u] && v != son[u])
  119. dfs2(v, v);   //沿轻边搭建新的重链
  120. }
  121. }
  122. void init() {
  123. dfs1(root, root);
  124. dfs2(root, root);
  125. build(1, 1, n);
  126. }
  127. inline void Msub(int u, int del) {
  128. modify(1, 1, n, id[u], id[u] + size[u] - 1, del);
  129. }
  130. inline int Qsub(int u) {
  131. return query(1, 1, n, id[u], id[u] + size[u] - 1) % mod;
  132. }
  133. void Mrange(int u, int v, int del) {
  134. while (ftop[u] != ftop[v]) {
  135. if (d[ftop[u]] < d[ftop[v]])
  136. swap(u, v);
  137. modify(1, 1, n, id[ftop[u]], id[u], del);
  138. u = f[ftop[u]];
  139. }
  140. if (d[u] > d[v]) swap(u, v);
  141. modify(1, 1, n, id[u], id[v], del);
  142. }
  143. int Qrange(int u, int v) {
  144. int ans = 0;
  145. while (ftop[u] != ftop[v]) {
  146. if (d[ftop[u]] < d[ftop[v]])
  147. swap(u, v);
  148. ans = (ans + query(1, 1, n, id[ftop[u]], id[u])) % mod;
  149. u = f[ftop[u]];
  150. }
  151. if (d[u] > d[v]) swap(u, v);
  152. ans = (ans + query(1, 1, n, id[u], id[v])) % mod;
  153. return ans;
  154. }
  155. } using namespace Divtree;
  156. int main() {
  157. read(n), read(m), read(root), read(mod);
  158. int u, v, del;
  159. for (int i = 1; i <= n; ++i)
  160. read(val[i]);
  161. for (int i = 1; i < n; ++i) {
  162. read(u), read(v);
  163. insert(u, v), insert(v, u);
  164. }
  165. init();
  166. register char ch;
  167. while (m--) {
  168. ch = getchar();
  169. while (!isdigit(ch)) ch = getchar();
  170. if (ch == '1') {
  171. read(u), read(v), read(del);
  172. Mrange(u, v, del);
  173. } else if (ch == '2') {
  174. read(u), read(v);
  175. printf("%d\n", Qrange(u, v));
  176. } else if (ch == '3') {
  177. read(u), read(del);
  178. Msub(u, del);
  179. } else {
  180. read(u);
  181. printf("%d\n", Qsub(u));
  182. }
  183. }
  184. return 0;
  185. }

最新文章

  1. 全面分析 Spring 的编程式事务管理及声明式事务管理
  2. HttpURlconntiuon获取网络数据
  3. Appium运行时,error: Logcat capture failed: spawn ENOENT的解决办法
  4. VB远程访问MYSQL代码图解
  5. DBLink创建 ORA-12154: TNS: 无法解析指定的连接标识符
  6. MVC解决方案发布IIS 登录页面需要输入两次帐号问题
  7. BZOJ-2186 沙拉公主的困惑 线性筛(筛筛筛)+线性推逆元
  8. WINDOWS和Linux上安装php7 alpha 并安装 yaf
  9. 跳到下个View
  10. Magento修改css样式更新之——grunt命令使用
  11. Agile.Net 组件式开发平台 - 权限管理组件
  12. struts2 测试错题解析
  13. unix c 05
  14. GCD 延时操作
  15. URAL 1963 Kite 四边形求对称轴数
  16. zookeeper源码分析-server-util
  17. 网络协议抓包分析——ARP地址解析协议
  18. jquery-网站收藏
  19. crm 理解
  20. Python+Selenium学习--简单对象定位

热门文章

  1. 还在本地安装MySQL/RabbitMQ/MongoDB 吗 ? 或许你可以试试这个【附下载】
  2. E. Copying Data 解析(線段樹)
  3. Docker(9)- docker pull 命令详解
  4. Spring Cloud 纯干货,从入门到实战
  5. leetcode 98:n-queens-ii
  6. 利用GitHub和Hexo打造免费的个人博客
  7. Docker - 解决 Error response from daemon: driver failed programming external connectivity on endpoint tomcat9999
  8. ubutun下安装phantomjs配置chromedriver
  9. read/write系统调用
  10. java服务器部署开源项目(若依)