左边可以得到某个信息,右边可以得到某个信息,父节点的信息可以通过左右子信息在O(1)时间内整合得到的可以使用线段树。

    1. public class Code_SegmentTree {
    2. // 线段树,时间复杂度logN,用于区间内更新,增加,查询
    3. public static class SegmentTree {
    4. // arr[]为原序列的信息从0开始,但在arr里是从1开始的
    5. // sum[]模拟线段树维护区间和
    6. // lazy[]为累加和懒惰标记
    7. // change[]为更新的值
    8. // update[]为更新慵懒标记
    9. private int MAXN;
    10. private int[] arr; // 缓的数组,原数组的备份数组
    11. private int[] sum; // 累加和数组,
    12. private int[] lazy;// 懒信息
    13. // 更新相关的数组
    14. private int[] change; // 一个范围上的值变成了啥
    15. private boolean[] update; // 区分更新值的标记true表示change对应位置为更新的数,false表示该位置没更新无效。
    16. //
    17. public SegmentTree(int[] origin) {
    18. MAXN = origin.length + 1;
    19. arr = new int[MAXN]; // arr[0] 不用 从1开始使用
    20. for (int i = 1; i < MAXN; i++) {
    21. arr[i] = origin[i - 1];
    22. }
    23. sum = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围的累加和信息,原数组*4
    24. lazy = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围沒有往下傳遞的纍加任務
    25. change = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围有没有更新操作的任务
    26. update = new boolean[MAXN << 2]; // 用来支持脑补概念中,某一个范围更新任务,更新成了什么
    27. }
    28. // 根据rt左右孩子的值,计算当前节点的sum数组值
    29. private void pushUp(int rt) {
    30. sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; // i*2 i*2+1
    31. }
    32. // 之前的,所有懒增加,和懒更新,从父范围,发给左右两个子范围
    33. // 分发策略是什么
    34. // rt 表示父节点
    35. // ln表示左子树元素结点个数,rn表示右子树结点个数
    36. private void pushDown(int rt, int ln, int rn) {
    37. // 下发更新任务
    38. if (update[rt]) {
    39. // 左右两个孩子都更新
    40. update[rt << 1] = true;
    41. update[rt << 1 | 1] = true;
    42. change[rt << 1] = change[rt];
    43. change[rt << 1 | 1] = change[rt];
    44. // 清空左右两个孩子的懒信息
    45. lazy[rt << 1] = 0;
    46. lazy[rt << 1 | 1] = 0;
    47. // 重新设置累加和信息
    48. sum[rt << 1] = change[rt] * ln;
    49. sum[rt << 1 | 1] = change[rt] * rn;
    50. update[rt] = false;
    51. }
    52. // 下发增加任务,懒数组更新,
    53. if (lazy[rt] != 0) {
    54. // 左孩子累加和数组和懒数组更新
    55. lazy[rt << 1] += lazy[rt];
    56. sum[rt << 1] += lazy[rt] * ln;
    57. // 右孩子累加和和数组更新
    58. lazy[rt << 1 | 1] += lazy[rt];
    59. sum[rt << 1 | 1] += lazy[rt] * rn;
    60. // 清空父节点rt的懒信息
    61. lazy[rt] = 0;
    62. }
    63. }
    64. // 在初始化阶段,先把sum数组,填好
    65. // 在arr[l~r]范围上,去build,1~N,
    66. // rt : 这个范围在sum中的下标
    67. public void build(int l, int r, int rt) {
    68. if (l == r) {
    69. sum[rt] = arr[l];
    70. return;
    71. }
    72. int mid = (l + r) >> 1; // 找到中位数
    73. build(l, mid, rt << 1); // 左孩子范围上build
    74. build(mid + 1, r, rt << 1 | 1); // 右孩子范围上build
    75. pushUp(rt); // 计算sum数组rt位置的值
    76. }
    77. // L~R 所有的值变成C
    78. // l~r rt,当前来到l,r上,对应下表rt
    79. public void update(int L, int R, int C, int l, int r, int rt) {
    80. // 任务包含所有的值,有更新信息
    81. if (L <= l && r <= R) {
    82. update[rt] = true;
    83. change[rt] = C;
    84. // 设置新的累加和
    85. sum[rt] = C * (r - l + 1);
    86. // 对应的懒数组位置清空
    87. lazy[rt] = 0;
    88. return;
    89. }
    90. // 当前任务躲不掉,无法懒更新,要往下发
    91. int mid = (l + r) >> 1;
    92. pushDown(rt, mid - l + 1, r - mid);
    93. if (L <= mid) {
    94. update(L, R, C, l, mid, rt << 1);
    95. }
    96. if (R > mid) {
    97. update(L, R, C, mid + 1, r, rt << 1 | 1);
    98. }
    99. pushUp(rt);
    100. }
    101. // L~R, C 任务!L-R范围上每个值都加C,L,R,C在递归过程中不变
    102. // rt,l~r,当前来到rt格子, 表示范围,l-r上的累加和
    103. public void add(int L, int R, int C, int l, int r, int rt) {
    104. // 任务如果把此时的范围全包了!
    105. if (L <= l && r <= R) {
    106. sum[rt] += C * (r - l + 1); // 更新l-r上的累加和
    107. lazy[rt] += C; // 懒数组更新
    108. return;
    109. }
    110. // 任务没有把此时的范围全包!懒不住,任务下发
    111. // l r mid = (l+r)/2
    112. int mid = (l + r) >> 1;
    113. pushDown(rt, mid - l + 1, r - mid); // 下发一层任务
    114. // L~R 派发新任务,
    115. if (L <= mid) { // 如果任务需要发给左边
    116. // 左侧执行新任务
    117. add(L, R, C, l, mid, rt << 1);
    118. }
    119. if (R > mid) { // 如果任务需要发给右边
    120. // 右侧执行新任务
    121. add(L, R, C, mid + 1, r, rt << 1 | 1);
    122. }
    123. // 更新自己的累加和
    124. pushUp(rt);
    125. }
    126. // 1~6 累加和是多少? 1~8 rt
    127. public long query(int L, int R, int l, int r, int rt) {
    128. if (L <= l && r <= R) {
    129. return sum[rt];
    130. }
    131. int mid = (l + r) >> 1;
    132. // 下发任务,为了配合add和update需要进行任务派发。
    133. pushDown(rt, mid - l + 1, r - mid);
    134. long ans = 0;
    135. if (L <= mid) {
    136. ans += query(L, R, l, mid, rt << 1);
    137. }
    138. if (R > mid) {
    139. ans += query(L, R, mid + 1, r, rt << 1 | 1);
    140. }
    141. return ans;
    142. }
    143. }
    144. // 纯暴力方法---测试使用 时间复杂度logN
    145. public static class Right {
    146. public int[] arr;
    147. public Right(int[] origin) {
    148. arr = new int[origin.length + 1];
    149. for (int i = 0; i < origin.length; i++) {
    150. arr[i + 1] = origin[i];
    151. }
    152. }
    153. public void update(int L, int R, int C) {
    154. for (int i = L; i <= R; i++) {
    155. arr[i] = C;
    156. }
    157. }
    158. public void add(int L, int R, int C) {
    159. for (int i = L; i <= R; i++) {
    160. arr[i] += C;
    161. }
    162. }
    163. public long query(int L, int R) {
    164. long ans = 0;
    165. for (int i = L; i <= R; i++) {
    166. ans += arr[i];
    167. }
    168. return ans;
    169. }
    170. }
    171. public static int[] genarateRandomArray(int len, int max) {
    172. int size = (int) (Math.random() * len) + 1;
    173. int[] origin = new int[size];
    174. for (int i = 0; i < size; i++) {
    175. origin[i] = (int) (Math.random() * max) - (int) (Math.random() * max);
    176. }
    177. return origin;
    178. }
    179. public static boolean test() {
    180. int len = 100;
    181. int max = 1000;
    182. int testTimes = 5000;
    183. int addOrUpdateTimes = 1000;
    184. int queryTimes = 500;
    185. for (int i = 0; i < testTimes; i++) {
    186. int[] origin = genarateRandomArray(len, max);
    187. SegmentTree seg = new SegmentTree(origin);
    188. int S = 1;
    189. int N = origin.length;
    190. int root = 1;
    191. seg.build(S, N, root);
    192. Right rig = new Right(origin);
    193. for (int j = 0; j < addOrUpdateTimes; j++) {
    194. int num1 = (int) (Math.random() * N) + 1;
    195. int num2 = (int) (Math.random() * N) + 1;
    196. int L = Math.min(num1, num2);
    197. int R = Math.max(num1, num2);
    198. int C = (int) (Math.random() * max) - (int) (Math.random() * max);
    199. if (Math.random() < 0.5) {
    200. seg.add(L, R, C, S, N, root);
    201. rig.add(L, R, C);
    202. } else {
    203. seg.update(L, R, C, S, N, root);
    204. rig.update(L, R, C);
    205. }
    206. }
    207. for (int k = 0; k < queryTimes; k++) {
    208. int num1 = (int) (Math.random() * N) + 1;
    209. int num2 = (int) (Math.random() * N) + 1;
    210. int L = Math.min(num1, num2);
    211. int R = Math.max(num1, num2);
    212. long ans1 = seg.query(L, R, S, N, root);
    213. long ans2 = rig.query(L, R);
    214. if (ans1 != ans2) {
    215. return false;
    216. }
    217. }
    218. }
    219. return true;
    220. }
    221. public static void main(String[] args) {
    222. int[] origin = { 2, 1, 1, 2, 3, 4, 5 };
    223. SegmentTree seg = new SegmentTree(origin);
    224. int S = 1; // 整个区间的开始位置,规定从1开始,不从0开始 -> 固定
    225. int N = origin.length; // 整个区间的结束位置,规定能到N,不是N-1 -> 固定
    226. int root = 1; // 整棵树的头节点位置,规定是1,不是0 -> 固定
    227. int L = 2; // 操作区间的开始位置 -> 可变
    228. int R = 5; // 操作区间的结束位置 -> 可变
    229. int C = 4; // 要加的数字或者要更新的数字 -> 可变
    230. // 区间生成,必须在[S,N]整个范围上build
    231. seg.build(S, N, root);
    232. // 区间修改,可以改变L、R和C的值,其他值不可改变
    233. seg.add(L, R, C, S, N, root);
    234. // 区间更新,可以改变L、R和C的值,其他值不可改变
    235. seg.update(L, R, C, S, N, root);
    236. // 区间查询,可以改变L和R的值,其他值不可改变
    237. long sum = seg.query(L, R, S, N, root);
    238. System.out.println(sum);
    239. System.out.println("对数器测试开始...");
    240. System.out.println("测试结果 : " + (test() ? "通过" : "未通过"));
    241. }
    242. }