• 动态树的异或和


    一 问题描述

    给定 n 个节点及每个节点的权值,节点编号为 1~n ,处理 m 种操作。操作格式:

    ① 0 x y ,查询 x 到y 路径上点的权值的 xor 和,保证 x 到 y 是连通的。

    ② 1 x y ,连接 x 到 y ,若 x 到 y 已经连通,则无须连接。

    ③ 2 x y ,删除边(x , y),不保证边 (x , y ) 存在。

    ④ 3 x y ,将节点x 的权值变成y 。

    二 输入和输出

    1 输入

    第 1 行包含两个整数 n 和 m ,表示节点数和操作数,1≤n≤10^5 ,1≤m≤3×10^5 ;接下来的 n 行,每行都包含一个 [1, 10^9 ]的整数,代表节点的权值;最后的 m 行,每行都包含 3 个整数,表示一种操作。

    2 输出

    对每个查询操作,都单行输出一个整数,表示 x 到 y 路径上点权的 xor 和。

    三 输入和输出样例

    1 输入样例

    3 3

    1

    2

    3

    1 1 2

    0 1 2

    0 1 1

    2 输出样例

    3

    1

    四 分析

    本问题为典型的动态树基本操作,包括路径上的权值 xor、连边、删边、点更新。

    五 算法设计

    0 x y :询问 x 到 y 路径上点的权值的 xor 和。首先切分 x -y 路径,然后返回树根 y 的 v 值即可(在旋转过程中更新 xor)。

    1 x y :连接 x、y 。先判断 x 、y 的连通性,若不连通,则连接 x 、y 。若 x 到 y 已经连通,则无须连接。

    2 x y :删除边 (x , y )。先判断 x 、y 的连通性,若连通且 x、y 之间有边,则删除 x 、y 之间的边。

    3 x y :将节点 x 的权值变成 y 。将 x 旋转到树根,然后令a[x ]=y 。

    六 代码

    1. package com.platform.modules.alg.alglib.p3690;
    2. public class P3690 {
    3. private int MAXN = 300005;
    4. int n, m;
    5. int a[] = new int[MAXN];
    6. int top;
    7. int c[][] = new int[MAXN][2];
    8. int fa[] = new int[MAXN];
    9. int v[] = new int[MAXN];
    10. int st[] = new int[MAXN];
    11. int rev[] = new int[MAXN];
    12. public String output = "";
    13. // 更新当前节点的值(路径上点值 XOR )
    14. void update(int x) {
    15. v[x] = v[c[x][0]] ^ v[c[x][1]] ^ a[x];
    16. }
    17. // 下传懒惰标记
    18. void pushdown(int x) {
    19. if (rev[x] == 1) {
    20. rev[c[x][0]] ^= 1;
    21. rev[c[x][1]] ^= 1;
    22. rev[x] ^= 1;
    23. int temp = c[x][0];
    24. c[x][0] = c[x][1];
    25. c[x][1] = temp;
    26. }
    27. }
    28. // 判断是否是所在 Splay 的根节点
    29. boolean isroot(int x) {
    30. return c[fa[x]][0] != x && c[fa[x]][1] != x;
    31. }
    32. // 旋转,将x变成y的父节点
    33. void rotate(int x) {
    34. int y = fa[x], z = fa[y], k;
    35. if (c[y][0] == x) {
    36. k = 1;
    37. } else {
    38. k = 0;
    39. }
    40. // 如果 y 不是根节点,那么将 z 的儿子 y 变成 x
    41. if (!isroot(y)) c[z][c[z][1] == y ? 1 : 0] = x;
    42. fa[x] = z;
    43. fa[y] = x;
    44. fa[c[x][k]] = y;
    45. c[y][k == 0 ? 1 : 0] = c[x][k];
    46. c[x][k] = y;
    47. update(y);
    48. update(x);
    49. }
    50. void splay(int x) {
    51. st[top = 1] = x;
    52. for (int i = x; !isroot(i); i = fa[i]) st[++top] = fa[i];//一定要从上往下
    53. while (top > 0) pushdown(st[top--]);
    54. while (!isroot(x)) {//将x旋到根
    55. int y = fa[x], z = fa[y];
    56. if (!isroot(y)) {
    57. if (c[y][0] == x ^ c[z][0] == y) {
    58. rotate(x);
    59. } else {
    60. rotate(y);
    61. }
    62. }
    63. rotate(x);
    64. }
    65. }
    66. // 连接一条 x 到根的实链
    67. void access(int x) {
    68. for (int y = 0; x > 0; x = fa[y = x]) {
    69. splay(x);
    70. c[x][1] = y;
    71. update(x);
    72. }
    73. }
    74. // 换根,将x变成原树的根
    75. void makeroot(int x) {
    76. access(x);
    77. splay(x);
    78. rev[x] ^= 1;
    79. }
    80. // 找 x 的根节点
    81. int findroot(int x) {
    82. access(x);
    83. splay(x);
    84. while (c[x][0] > 0) x = c[x][0];
    85. return x;
    86. }
    87. // 拉出一条 x 到 y 的路径为一个 Splay
    88. void split(int x, int y) {
    89. makeroot(x);
    90. access(y);
    91. splay(y);
    92. }
    93. // 删除一条 x 到 y 的边
    94. void cut(int x, int y) {
    95. split(x, y);
    96. if (c[y][0] != x || c[x][1] > 0) return; // 坑点,此时 x 是 y 的左儿子,且 x 没有右儿子,如果有说明x--y中间有节点
    97. c[y][0] = fa[c[y][0]] = 0;
    98. update(y);
    99. }
    100. // 连一条 x 到 y 的虚边
    101. void link(int x, int y) {
    102. makeroot(x);
    103. fa[x] = y;
    104. }
    105. public String cal(String input) {
    106. String[] line = input.split("\n");
    107. String[] word = line[0].split(" ");
    108. n = Integer.parseInt(word[0]);
    109. m = Integer.parseInt(word[1]);
    110. for (int i = 1; i <= n; i++) {
    111. a[i] = Integer.parseInt(line[i]);
    112. v[i] = a[i];
    113. }
    114. int count = 1;
    115. while (m-- > 0) {
    116. int opt, x, y;
    117. String[] query = line[n + count++].split(" ");
    118. opt = Integer.parseInt(query[0]);
    119. x = Integer.parseInt(query[1]);
    120. y = Integer.parseInt(query[2]);
    121. if (opt == 0) {
    122. split(x, y);
    123. output += v[y] + "\n";
    124. } else if (opt == 1) {
    125. if (findroot(x) != findroot(y)) // 不连通
    126. link(x, y);
    127. } else if (opt == 2) {
    128. if (findroot(x) == findroot(y)) // 连通
    129. cut(x, y);
    130. } else if (opt == 3) {
    131. splay(x);
    132. a[x] = y;
    133. }
    134. }
    135. return output;
    136. }
    137. }

    七 测试

  • 相关阅读:
    java中的深度复制和浅复制的BUG
    C++内存空间
    一文彻底弄懂Linux-Shell编程
    【AutoSAR CAN】02 - 硬件过滤器配置
    SpringCloud Alibaba学习笔记,记重点!!
    Java8 新特性 函数式接口
    最短路Dijkstra和最小生成树Prim算法的同异
    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记
    error: invalid path ‘drivers/gpu/drm/nouveau/nvkm/subdev/i2c/aux.c‘
    (四)stm32之通信协议
  • 原文地址:https://blog.csdn.net/chengqiuming/article/details/127871418