• 树的统计问题


    一 问题描述

    一棵树有 n 个节点,编号为 1~n,每个节点都有一个权值 w 。完成以下操作。

    ① CHANGE u t ,把节点 u 的权值修改为 t 

    ② QMAX u v ,询问从节点 u 到节点 v 路径上节点的最大权值

    ③ QSUM u v ,询问从节点 u 到节点 v 路径上节点的权值和

    注意:从节点 u 到节点 v 路径上的节点包括 u 和 v 自身。

    二 输入和输出说明

    1 输入说明

    第 1 行包含一个整数 n ,表示节点的个数。接下来的 n -1 行,每行都包含两个整数 a 和 b ,表示在节点 a 和节点 b 之间有一条边相连。接下来的 n 行,第 i 行的整数 wi 表示节点 i 的权值。接下来的一行包含一个整数 q ,表示操作的总数。最后有 q 行,每行都表示一种操作,操作形式如上所述 。 其中,1≤n≤30000,0≤q≤200000,保证操作中每个节点的权值 w 都为 -30000~ 30000。

    2 输出说明

    对每个 QMAX 或者 QSUM 的操作,都单行输出一个整数,表示要求的结果。

    三 输入和输出样例

    1 输入样例

    1 2 

    2 3 

    4 1 

    4 2 1 3 

    12 

    QMAX 3 4 

    QMAX 3 3 

    QMAX 3 2 

    QMAX 2 3 

    QSUM 3 4 

    QSUM 2 1 

    CHANGE 1 5 

    QMAX 3 4 

    CHANGE 3 6 

    QMAX 3 4 

    QMAX 2 4 

    QSUM 3 4

    2 输出样例

    10 

    16

    四 分析

    本问题包括树上点更新、区间最值、区间和值查询。可以用树链剖分将树形结构线性化,然后用线段树进行点更新、区间最值、区间和值查询。解决方案:树链剖分+线段树。

    五 算法设计

    1 第 1 次深度优先遍历求 dep、fa、size、son,第 2 次深度优先遍历求 top、id、rev;

    2 创建线段树

    3 点更新,u 对应的下标 i = id[u],在线段树中将该下标的值更新为 val;

    4 区间查询,求 u 、v 之间的最值与和值。若 u 、v 不在同一条重链上,则一边查询,一边向同一条重链靠拢;若 u 、v 在同一条重链上,则根据节点的下标在线段树中进行区间查询。

    六 代码

    1. package com.platform.modules.alg.alglib.hysbz1036;
    2. public class Hysbz1036 {
    3. public String output = "";
    4. private int maxn = 30005;
    5. int n, m; // n 个结点,m 个查询
    6. int head[] = new int[maxn]; // 头结点
    7. int cnt = 0, total = 0;
    8. int fa[] = new int[maxn]; // 父亲
    9. int dep[] = new int[maxn]; // 深度
    10. int size[] = new int[maxn]; // 子树结点总数
    11. int son[] = new int[maxn]; // 重儿子
    12. int top[] = new int[maxn]; // 所在重链顶端结点
    13. int w[] = new int[maxn]; // 权值
    14. int id[] = new int[maxn];
    15. int rev[] = new int[maxn]; // u 对应的 dfs 序下标,下标对于的 u
    16. int Max, Sum;
    17. edge e[] = new edge[maxn << 1];
    18. // 树结点存储数组
    19. node tree[] = new node[maxn * 4];
    20. void add(int u, int v) {
    21. e[++cnt].to = v;
    22. e[cnt].next = head[u];
    23. head[u] = cnt;
    24. }
    25. // 求 dep,fa,size,son
    26. void dfs1(int u, int f) {
    27. size[u] = 1;
    28. for (int i = head[u]; i > 0; i = e[i].next) {
    29. int v = e[i].to;
    30. if (v == f) // 父节点
    31. continue;
    32. dep[v] = dep[u] + 1; // 深度
    33. fa[v] = u;
    34. dfs1(v, u);
    35. size[u] += size[v];
    36. if (size[v] > size[son[u]])
    37. son[u] = v;
    38. }
    39. }
    40. // 求 top,id,rev
    41. void dfs2(int u, int t) {
    42. top[u] = t;
    43. id[u] = ++total; // u 对应的 dfs 序下标
    44. rev[total] = u; // dfs 序下标对应的结点 u
    45. if (son[u] == 0)
    46. return;
    47. dfs2(son[u], t); // 沿着重儿子 dfs
    48. for (int i = head[u]; i > 0; i = e[i].next) {
    49. int v = e[i].to;
    50. if (v != fa[u] && v != son[u])
    51. dfs2(v, v);
    52. }
    53. }
    54. // 创建线段树,k 表示存储下标,区间 [l,r]
    55. void build(int k, int l, int r) {
    56. tree[k].l = l;
    57. tree[k].r = r;
    58. if (l == r) {
    59. tree[k].mx = tree[k].sum = w[rev[l]];
    60. return;
    61. }
    62. int mid, lc, rc;
    63. mid = (l + r) / 2; // 划分点
    64. lc = k * 2; // 左孩子存储下标
    65. rc = k * 2 + 1; // 右孩子存储下标
    66. build(lc, l, mid);
    67. build(rc, mid + 1, r);
    68. tree[k].mx = Math.max(tree[lc].mx, tree[rc].mx); // 结点的最大值等于左右孩子最值的最大值
    69. tree[k].sum = tree[lc].sum + tree[rc].sum; // 结点的和值等于左右子树和值
    70. }
    71. // 求区间[l..r]的最值、和值
    72. void query(int k, int l, int r) {
    73. if (tree[k].l >= l && tree[k].r <= r) { // 找到该区间
    74. Max = Math.max(Max, tree[k].mx);
    75. Sum += tree[k].sum;
    76. return;
    77. }
    78. int mid, lc, rc;
    79. mid = (tree[k].l + tree[k].r) / 2;//划分点
    80. lc = k * 2; //左孩子存储下标
    81. rc = k * 2 + 1;//右孩子存储下标
    82. if (l <= mid)
    83. query(lc, l, r);//到左子树查询
    84. if (r > mid)
    85. query(rc, l, r);//到右子树查询
    86. }
    87. void ask(int u, int v) {//求u,v之间的最值或和值
    88. while (top[u] != top[v]) {//不在同一条重链上
    89. if (dep[top[u]] < dep[top[v]]) {
    90. int temp = u;
    91. u = v;
    92. v = temp;
    93. }
    94. query(1, id[top[u]], id[u]); // u 顶端结点和 u之间
    95. u = fa[top[u]];
    96. }
    97. if (dep[u] > dep[v]) { // 在同一条重链上
    98. int temp = u;
    99. u = v;
    100. v = temp;
    101. }
    102. query(1, id[u], id[v]);
    103. }
    104. void update(int k, int i, int val) {//u对应的下标i,将其值修改更新为val
    105. if (tree[k].l == tree[k].r && tree[k].l == i) {//找到i
    106. tree[k].mx = tree[k].sum = val;
    107. return;
    108. }
    109. int mid, lc, rc;
    110. mid = (tree[k].l + tree[k].r) / 2;//划分点
    111. lc = k * 2; //左孩子存储下标
    112. rc = k * 2 + 1;//右孩子存储下标
    113. if (i <= mid)
    114. update(lc, i, val);//到左子树修改更新
    115. else
    116. update(rc, i, val);//到右子树修改更新
    117. tree[k].mx = Math.max(tree[lc].mx, tree[rc].mx);//返回时修改更新最值
    118. tree[k].sum = tree[lc].sum + tree[rc].sum;//返回时修改更新和值
    119. }
    120. public Hysbz1036() {
    121. for (int i = 0; i < e.length; i++) {
    122. e[i] = new edge();
    123. }
    124. for (int i = 0; i < tree.length; i++) {
    125. tree[i] = new node();
    126. }
    127. }
    128. public String cal(String input) {
    129. int x, y;
    130. String str;
    131. String[] line = input.split("\n");
    132. String[] words = line[0].split(" ");
    133. n = Integer.parseInt(words[0]);
    134. for (int i = 1; i < n; i++) {
    135. String[] num = line[i].split(" ");
    136. x = Integer.parseInt(num[0]);
    137. y = Integer.parseInt(num[1]);
    138. add(x, y);
    139. add(y, x);
    140. }
    141. String[] wage = line[n].split(" ");
    142. for (int i = 1; i <= n; i++)
    143. w[i] = Integer.parseInt(wage[i-1]);
    144. dep[1] = 1;
    145. dfs1(1, 0);
    146. dfs2(1, 1);
    147. build(1, 1, total);//创建线段树
    148. m = Integer.parseInt(line[n + 1]);
    149. for (int i = n + 2; i <= m + n + 1; i++) {
    150. String[] query = line[i].split(" ");
    151. str = query[0];
    152. x = Integer.parseInt(query[1]);
    153. y = Integer.parseInt(query[2]);
    154. if (str.charAt(0) == 'C')
    155. update(1, id[x], y);
    156. else {
    157. Sum = 0;
    158. Max = -0x3f3f3f3f;
    159. ask(x, y);
    160. if (str.charAt(1) == 'M')
    161. output += Max + "\n";
    162. else
    163. output += Sum + "\n";
    164. }
    165. }
    166. return output;
    167. }
    168. }
    169. class edge {
    170. int to, next;
    171. }
    172. // 结点
    173. class node {
    174. int l, r, mx, sum; // l,r 表示区间左右端点,mx 表示区间 [l,r] 的最值
    175. }

    七 测试

  • 相关阅读:
    vue实战入门后台篇十:springboot+mybatis实现网站后台-项目整合发布测试
    python可视化分析(五)-绘制边缘箱线图
    Redis持久化
    nmap各种扫描的注意事项
    使用Blender编辑Character Creater 4的人物形象
    Git 基本原理和常用操作
    cocos:MotionStreak拖尾渐隐效果
    uniapp的webview实现左滑返回上一个页面
    .NET使用quartz+topshelf实现定时执行任务调度服务
    2023.10.20期中考核复现(misc)
  • 原文地址:https://blog.csdn.net/chengqiuming/article/details/127696241