左边可以得到某个信息,右边可以得到某个信息,父节点的信息可以通过左右子信息在O(1)时间内整合得到的可以使用线段树。
public class Code_SegmentTree {// 线段树,时间复杂度logN,用于区间内更新,增加,查询public static class SegmentTree {// arr[]为原序列的信息从0开始,但在arr里是从1开始的// sum[]模拟线段树维护区间和// lazy[]为累加和懒惰标记// change[]为更新的值// update[]为更新慵懒标记private int MAXN;private int[] arr; // 缓的数组,原数组的备份数组private int[] sum; // 累加和数组,private int[] lazy;// 懒信息// 更新相关的数组private int[] change; // 一个范围上的值变成了啥private boolean[] update; // 区分更新值的标记true表示change对应位置为更新的数,false表示该位置没更新无效。//public SegmentTree(int[] origin) {MAXN = origin.length + 1;arr = new int[MAXN]; // arr[0] 不用 从1开始使用for (int i = 1; i < MAXN; i++) {arr[i] = origin[i - 1];}sum = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围的累加和信息,原数组*4lazy = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围沒有往下傳遞的纍加任務change = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围有没有更新操作的任务update = new boolean[MAXN << 2]; // 用来支持脑补概念中,某一个范围更新任务,更新成了什么}// 根据rt左右孩子的值,计算当前节点的sum数组值private void pushUp(int rt) {sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; // i*2 i*2+1}// 之前的,所有懒增加,和懒更新,从父范围,发给左右两个子范围// 分发策略是什么// rt 表示父节点// ln表示左子树元素结点个数,rn表示右子树结点个数private void pushDown(int rt, int ln, int rn) {// 下发更新任务if (update[rt]) {// 左右两个孩子都更新update[rt << 1] = true;update[rt << 1 | 1] = true;change[rt << 1] = change[rt];change[rt << 1 | 1] = change[rt];// 清空左右两个孩子的懒信息lazy[rt << 1] = 0;lazy[rt << 1 | 1] = 0;// 重新设置累加和信息sum[rt << 1] = change[rt] * ln;sum[rt << 1 | 1] = change[rt] * rn;update[rt] = false;}// 下发增加任务,懒数组更新,if (lazy[rt] != 0) {// 左孩子累加和数组和懒数组更新lazy[rt << 1] += lazy[rt];sum[rt << 1] += lazy[rt] * ln;// 右孩子累加和和数组更新lazy[rt << 1 | 1] += lazy[rt];sum[rt << 1 | 1] += lazy[rt] * rn;// 清空父节点rt的懒信息lazy[rt] = 0;}}// 在初始化阶段,先把sum数组,填好// 在arr[l~r]范围上,去build,1~N,// rt : 这个范围在sum中的下标public void build(int l, int r, int rt) {if (l == r) {sum[rt] = arr[l];return;}int mid = (l + r) >> 1; // 找到中位数build(l, mid, rt << 1); // 左孩子范围上buildbuild(mid + 1, r, rt << 1 | 1); // 右孩子范围上buildpushUp(rt); // 计算sum数组rt位置的值}// L~R 所有的值变成C// l~r rt,当前来到l,r上,对应下表rtpublic void update(int L, int R, int C, int l, int r, int rt) {// 任务包含所有的值,有更新信息if (L <= l && r <= R) {update[rt] = true;change[rt] = C;// 设置新的累加和sum[rt] = C * (r - l + 1);// 对应的懒数组位置清空lazy[rt] = 0;return;}// 当前任务躲不掉,无法懒更新,要往下发int mid = (l + r) >> 1;pushDown(rt, mid - l + 1, r - mid);if (L <= mid) {update(L, R, C, l, mid, rt << 1);}if (R > mid) {update(L, R, C, mid + 1, r, rt << 1 | 1);}pushUp(rt);}// L~R, C 任务!L-R范围上每个值都加C,L,R,C在递归过程中不变// rt,l~r,当前来到rt格子, 表示范围,l-r上的累加和public void add(int L, int R, int C, int l, int r, int rt) {// 任务如果把此时的范围全包了!if (L <= l && r <= R) {sum[rt] += C * (r - l + 1); // 更新l-r上的累加和lazy[rt] += C; // 懒数组更新return;}// 任务没有把此时的范围全包!懒不住,任务下发// l r mid = (l+r)/2int mid = (l + r) >> 1;pushDown(rt, mid - l + 1, r - mid); // 下发一层任务// L~R 派发新任务,if (L <= mid) { // 如果任务需要发给左边// 左侧执行新任务add(L, R, C, l, mid, rt << 1);}if (R > mid) { // 如果任务需要发给右边// 右侧执行新任务add(L, R, C, mid + 1, r, rt << 1 | 1);}// 更新自己的累加和pushUp(rt);}// 1~6 累加和是多少? 1~8 rtpublic long query(int L, int R, int l, int r, int rt) {if (L <= l && r <= R) {return sum[rt];}int mid = (l + r) >> 1;// 下发任务,为了配合add和update需要进行任务派发。pushDown(rt, mid - l + 1, r - mid);long ans = 0;if (L <= mid) {ans += query(L, R, l, mid, rt << 1);}if (R > mid) {ans += query(L, R, mid + 1, r, rt << 1 | 1);}return ans;}}// 纯暴力方法---测试使用 时间复杂度logNpublic static class Right {public int[] arr;public Right(int[] origin) {arr = new int[origin.length + 1];for (int i = 0; i < origin.length; i++) {arr[i + 1] = origin[i];}}public void update(int L, int R, int C) {for (int i = L; i <= R; i++) {arr[i] = C;}}public void add(int L, int R, int C) {for (int i = L; i <= R; i++) {arr[i] += C;}}public long query(int L, int R) {long ans = 0;for (int i = L; i <= R; i++) {ans += arr[i];}return ans;}}public static int[] genarateRandomArray(int len, int max) {int size = (int) (Math.random() * len) + 1;int[] origin = new int[size];for (int i = 0; i < size; i++) {origin[i] = (int) (Math.random() * max) - (int) (Math.random() * max);}return origin;}public static boolean test() {int len = 100;int max = 1000;int testTimes = 5000;int addOrUpdateTimes = 1000;int queryTimes = 500;for (int i = 0; i < testTimes; i++) {int[] origin = genarateRandomArray(len, max);SegmentTree seg = new SegmentTree(origin);int S = 1;int N = origin.length;int root = 1;seg.build(S, N, root);Right rig = new Right(origin);for (int j = 0; j < addOrUpdateTimes; j++) {int num1 = (int) (Math.random() * N) + 1;int num2 = (int) (Math.random() * N) + 1;int L = Math.min(num1, num2);int R = Math.max(num1, num2);int C = (int) (Math.random() * max) - (int) (Math.random() * max);if (Math.random() < 0.5) {seg.add(L, R, C, S, N, root);rig.add(L, R, C);} else {seg.update(L, R, C, S, N, root);rig.update(L, R, C);}}for (int k = 0; k < queryTimes; k++) {int num1 = (int) (Math.random() * N) + 1;int num2 = (int) (Math.random() * N) + 1;int L = Math.min(num1, num2);int R = Math.max(num1, num2);long ans1 = seg.query(L, R, S, N, root);long ans2 = rig.query(L, R);if (ans1 != ans2) {return false;}}}return true;}public static void main(String[] args) {int[] origin = { 2, 1, 1, 2, 3, 4, 5 };SegmentTree seg = new SegmentTree(origin);int S = 1; // 整个区间的开始位置,规定从1开始,不从0开始 -> 固定int N = origin.length; // 整个区间的结束位置,规定能到N,不是N-1 -> 固定int root = 1; // 整棵树的头节点位置,规定是1,不是0 -> 固定int L = 2; // 操作区间的开始位置 -> 可变int R = 5; // 操作区间的结束位置 -> 可变int C = 4; // 要加的数字或者要更新的数字 -> 可变// 区间生成,必须在[S,N]整个范围上buildseg.build(S, N, root);// 区间修改,可以改变L、R和C的值,其他值不可改变seg.add(L, R, C, S, N, root);// 区间更新,可以改变L、R和C的值,其他值不可改变seg.update(L, R, C, S, N, root);// 区间查询,可以改变L和R的值,其他值不可改变long sum = seg.query(L, R, S, N, root);System.out.println(sum);System.out.println("对数器测试开始...");System.out.println("测试结果 : " + (test() ? "通过" : "未通过"));}}
