• 动态树的第2大值


    一 问题描述

    一棵有 N 个节点的树,节点编号为 1..N。每个节点都有一个权值。有 4 种类型的操作:

    ① 1 x y a b ,从树中删除一条边 x -y ,然后添加一条新边 a -b ;确保在添加新边后它仍然构成树。

    ② 2 a b w ,将节点 a 和 b 路径上所有节点(包括a 和b)的权值都修改为 w。

    ③ 3 a b d ,将节点 a 和 b(包括 a 和 b )路径上所有节点的权值都增加 d。 

    ④ 4 a b ,查询节点 a 和 b (包括 a 和 b )路径上的第 2 大权值,以及该权值在路径上出现的次数。

    注意:这里是严格的第 2 大权值。{3, 5, 2, 5, 3} 的严格第 2 大权值是 3。

    二 输入和输出

    1 输入

    第 1 行包含一个整数 T(T ≤ 3),表示测试用例数。每个测试用例的第 1 行都包含两个整数 N 和 M( N、M ≤10^5 ),表示节点数和操作数;第2行包含 N 个整数,表示节点的权值;接下来的 N-1 行,每行都包含两个整数 a 和 b ,表示在节点 a 和 b 之间有一条边;最后 M 行描述操作。其中,1≤x , y , a , b ≤N ,|w |≤10^4 ,|d|≤10^4 。

    2 输出

    对每个测试用例,都先单行输出“Case #x :”(x 表示测试用例编号);对每个查询操作都输出两个值,即第 2 大权值及其出现的次数。若路径上所有节点的权值都相同,则输出“ALL SAME”。

    三 输入和输出样例

    1 输入样例

    2

    3 2

    1 1 2

    1 2

    1 3

    4 1 2

    4 2 3

    7 7

    5 3 2 1 7 3 6

    1 2

    1 3

    3 4

    3 5

    4 6

    4 7

    4 2 6

    3 4 5 -1

    4 5 7

    1 3 4 2 4

    4 3 6

    2 3 6 5

    4 3 6

    2 输出样例

    Case #1:

    ALL SAME

    1 2

    Case #2:

    3 2

    1 1

    3 2

    ALL SAME

    四 图解

    根据输入样例 2,构建的树形结构如下图所示。

    它对应的 7 种操作如下。

    (1)4 2 6:查询 2-6 路径上的第 2 大权值和该权值的重复次数。2-6 路径上的第 2 大权值为 3,该权值的重复次数为 2,所以输出 3 2。

    (2)3 4 5 -1:将 4-5 路径上所有节点的权值都减 1。

    (3)4 5 7:查询 5-7 路径上的第 2 大权值和该权值的重复次数,路径 5-7 上的第 2 大权值为 1,该权值的重复次数为 1,输出 1 1。

    (4)1 3 4 2 4:删除 3-4 的边,添加 2-4 的边。

    (5)4 3 6:查询 3-6 路径上的第 2 大权值和该权值的重复次数,路径 3-6 上的第 2 大权值为 3,该权值的重复次数为 2,输出 3 2。

    (6)2 3 6 5:将 3-6 路径上所有节点的权值都修改为 5。

    (7)4 3 6:查询 3-6 路径上的第 2 大权值和该权值的重复次数,路径 3-6 上的所有权值都相同,输出“ALL SAME”。

    五 分析

    本问题为动态树问题,包括删边/连边、区间更新、区间修改、区间第 2 大值查询。

    六 算法设计

    1 删边/连边

    删除一条边 x -y ,然后添加一条新边 a -b 。只需执行 cut(x , y)、link(a , b)即可。

    2 区间更新

    将 a -b 路径上所有节点的权值都修改为 x 。首先执行 split(a , b),切分出 a -b 路径,然后执行 change(b , x ),将以 b 为根的子树上的所有节点都修改为x(打懒标记)。

    3 区间修改

    将 a -b 路径上所有节点的权值都增加 d 。首先执行 split(a , b),切分出 a -b 路径,然后执行 addval(b , d),将以 b 为根的子树上的所有节点都增加d(打懒标记)。

    4 区间第2大值查询。

    查询 a -b 路径上所有节点的第 2 大权值及该权值出现的次数。首先执行split(a , b),切分出 a-b 路径,然后执行 query(a , b),若第2大值的重复次数等于以 y 为根的子树大小,则输出“ALL SAME”,否则输出第 2 大值及其重复次数。

    七 代码

    1. package com.platform.modules.alg.alglib.hdu5002;
    2. /**
    3. * @className: Hdu5002
    4. * @description: 参考 http://hzwer.com/5540.html
    5. * @date: 2022/11/17
    6. * @author: chengqiuming
    7. */
    8. public class Hdu5002 {
    9. private int inf = 0x3f3f3f3f;
    10. private int N = 100005;
    11. int n, m, top;
    12. int c[][] = new int[N][2];
    13. int fa[] = new int[N];
    14. int v[] = new int[N];
    15. int st[] = new int[N];
    16. int mx1[] = new int[N]; // 最大值
    17. int mx2[] = new int[N]; // 次大值
    18. int c1[] = new int[N]; // 两者的重复次数
    19. int c2[] = new int[N]; // 子树大小
    20. int size[] = new int[N]; // 子树大小
    21. int ta[] = new int[N]; // 增加懒标记
    22. int tc[] = new int[N]; // 修改懒标记
    23. int rev[] = new int[N]; // 反转懒标记
    24. public String output = "";
    25. // 统计 x 的子树中最大、次大值和重复次数,当前节点值为 val,其重复次数为 c
    26. void cnt(int x, int val, int c) {
    27. if (val > mx1[x]) {
    28. mx2[x] = mx1[x];
    29. mx1[x] = val;
    30. c2[x] = c1[x];
    31. c1[x] = c;
    32. } else if (val == mx1[x]) {
    33. c1[x] += c;
    34. } else if (val > mx2[x]) {
    35. mx2[x] = val;
    36. c2[x] = c;
    37. } else if (val == mx2[x]) {
    38. c2[x] += c;
    39. }
    40. }
    41. // y 为根的子树上所有节点改为 val
    42. void change(int y, int val) {
    43. mx1[y] = val;
    44. v[y] = val;
    45. c1[y] = size[y];
    46. mx2[y] = -inf;
    47. c2[y] = 0;
    48. tc[y] = val;
    49. if (ta[y] != 0) ta[y] = 0;
    50. }
    51. // y 为根的子树上所有节点加 val
    52. void addval(int y, int val) {
    53. ta[y] += val;
    54. mx1[y] += val;
    55. v[y] += val;
    56. if (mx2[y] != -inf) mx2[y] += val;
    57. }
    58. void update(int x) {
    59. int l = c[x][0], r = c[x][1];
    60. mx1[x] = mx2[x] = -inf;
    61. c1[x] = c2[x] = 0;
    62. cnt(x, v[x], 1);
    63. if (l > 0) {
    64. cnt(x, mx1[l], c1[l]);
    65. cnt(x, mx2[l], c2[l]);
    66. }
    67. if (r > 0) {
    68. cnt(x, mx1[r], c1[r]);
    69. cnt(x, mx2[r], c2[r]);
    70. }
    71. size[x] = size[l] + size[r] + 1;
    72. }
    73. void pushdown(int x) {
    74. int l = c[x][0], r = c[x][1];
    75. if (rev[x] == 1) {//反转
    76. rev[x] ^= 1;
    77. rev[l] ^= 1;
    78. rev[r] ^= 1;
    79. // 交换两者的值,不要写l,r
    80. int temp = c[x][0];
    81. c[x][0] = c[x][1];
    82. c[x][1] = temp;
    83. }
    84. if (tc[x] != -inf) {//修改
    85. if (l > 0) change(l, tc[x]);
    86. if (r > 0) change(r, tc[x]);
    87. tc[x] = -inf;
    88. }
    89. if (ta[x] != 0) {//增加
    90. if (l > 0) addval(l, ta[x]);
    91. if (r > 0) addval(r, ta[x]);
    92. ta[x] = 0;
    93. }
    94. }
    95. boolean isroot(int x) {
    96. return c[fa[x]][0] != x && c[fa[x]][1] != x;
    97. }
    98. void rotate(int x) {
    99. int y = fa[x], z = fa[y], l, r;
    100. if (c[y][0] == x) l = 0;
    101. else l = 1;
    102. r = l ^ 1;
    103. if (!isroot(y)) {
    104. if (c[z][0] == y) c[z][0] = x;
    105. else c[z][1] = x;
    106. }
    107. fa[x] = z;
    108. fa[y] = x;
    109. fa[c[x][r]] = y;
    110. c[y][l] = c[x][r];
    111. c[x][r] = y;
    112. update(y);
    113. update(x);
    114. }
    115. void splay(int x) {
    116. top = 0;
    117. st[++top] = x;
    118. for (int i = x; !isroot(i); i = fa[i])
    119. st[++top] = fa[i];
    120. while (top > 0) pushdown(st[top--]);
    121. while (!isroot(x)) {
    122. int y = fa[x], z = fa[y];
    123. if (!isroot(y)) {
    124. if (c[y][0] == x ^ c[z][0] == y) {
    125. rotate(x);
    126. } else {
    127. rotate(y);
    128. }
    129. }
    130. rotate(x);
    131. }
    132. }
    133. void access(int x) {
    134. for (int t = 0; x > 0; t = x, x = fa[x]) {
    135. splay(x);
    136. c[x][1] = t;
    137. update(x);
    138. }
    139. }
    140. void makeroot(int x) {
    141. access(x);
    142. splay(x);
    143. rev[x] ^= 1;
    144. }
    145. void link(int x, int y) {
    146. makeroot(x);
    147. fa[x] = y;
    148. }
    149. void split(int x, int y) {
    150. makeroot(x);
    151. access(y);
    152. splay(y);
    153. }
    154. void cut(int x, int y) {
    155. split(x, y);
    156. c[y][0] = fa[c[y][0]] = 0;
    157. update(y);
    158. }
    159. int findroot(int x) {
    160. access(x);
    161. splay(x);
    162. while (c[x][0] > 0) x = c[x][0];
    163. return x;
    164. }
    165. void query(int x, int y) {
    166. if (c1[y] == size[y]) {
    167. output += "ALL SAME";
    168. } else {
    169. output += mx2[y] + " " + c2[y] + "\n";
    170. }
    171. }
    172. public String cal(String input) {
    173. int opt, x, y, a, b, d;
    174. String[] line = input.split("\n");
    175. String[] num = line[0].split(" ");
    176. n = Integer.parseInt(num[0]);
    177. m = Integer.parseInt(num[1]);
    178. String[] wage = line[1].split(" ");
    179. for (int i = 1; i <= n; i++) {
    180. v[i] = Integer.parseInt(wage[i - 1]);
    181. }
    182. for (int i = 1; i <= n; i++) {
    183. mx1[i] = v[i];
    184. c1[i] = 1;
    185. mx2[i] = -inf;
    186. c2[i] = 0;
    187. size[i] = 1;
    188. }
    189. for (int i = 1; i <= n; i++) {
    190. fa[i] = c[i][0] = c[i][1] = 0;
    191. ta[i] = rev[i] = 0;
    192. tc[i] = -inf;
    193. }
    194. for (int i = 1; i < n; i++) {
    195. String[] edge = line[i + 1].split(" ");
    196. x = Integer.parseInt(edge[0]);
    197. y = Integer.parseInt(edge[1]);
    198. link(x, y);
    199. }
    200. int count = 1;
    201. while (m-- > 0) {
    202. String[] query = line[n + count++].split(" ");
    203. opt = Integer.parseInt(query[0]);
    204. if (opt == 1) {
    205. x = Integer.parseInt(query[1]);
    206. y = Integer.parseInt(query[2]);
    207. a = Integer.parseInt(query[3]);
    208. b = Integer.parseInt(query[4]);
    209. cut(x, y);
    210. link(a, b);
    211. } else if (opt == 2) {
    212. a = Integer.parseInt(query[1]);
    213. b = Integer.parseInt(query[2]);
    214. x = Integer.parseInt(query[3]);
    215. split(a, b);
    216. change(b, x);
    217. } else if (opt == 3) {
    218. a = Integer.parseInt(query[1]);
    219. b = Integer.parseInt(query[2]);
    220. d = Integer.parseInt(query[3]);
    221. split(a, b);
    222. addval(b, d);
    223. } else {
    224. a = Integer.parseInt(query[1]);
    225. b = Integer.parseInt(query[2]);
    226. split(a, b);
    227. query(a, b);
    228. }
    229. }
    230. return output;
    231. }
    232. }

    八 测试

    1 输入

    7 7

    5 3 2 1 7 3 6

    1 2

    1 3

    3 4

    3 5

    4 6

    4 7

    4 2 6

    3 4 5 -1

    4 5 7

    1 3 4 2 4

    4 3 6

    2 3 6 5

    4 3 6

    2 输出

  • 相关阅读:
    面向防疫的智能导诊机器人关键技术及应用
    Dagger2系列详解(三):@privider 依赖
    2022上海生物发酵展-品牌企业纷纷入驻抢占先机,谁来赴盛宴参邀您的参与
    【附源码】计算机毕业设计JAVA家装建材网
    UNIAPP day_03(9.1)服务器端数据的异步请求
    医护上门系统—为老人和患者提供更舒适和现代化体验
    IO流高级流
    基于JAVA高校共享单车管理系统计算机毕业设计源码+数据库+lw文档+系统+部署
    聊聊秒杀系统的设计(一)
    嵌入式:驱动开发 Day2
  • 原文地址:https://blog.csdn.net/chengqiuming/article/details/127904217