Skip to content

线段树 ​

简介 ​

add(1,200,6) 范围上的增加 需要懒更新 lazy[]

apdate() 范围上的更新 需要update[] 和 change[]

query() 范围上的查询

​ [1 2 3 4]

​ 1-4累加和

 1-2累加和    3-4累计和

 1-1  2-2     3-3  4-4

累加和为一个数组,如果原数组数量为N,该数组4*N就够了

核心代码 ​

初始化 ​

java
        // 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;

		public SegmentTree(int[] origin) {
			MAXN = origin.length + 1;
			arr = new int[MAXN]; // arr[0] 不用 从1开始使用
            // 原始数组的0位置不用
			for (int i = 1; i < MAXN; i++) {
				arr[i] = origin[i - 1];
			}
			sum = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围的累加和信息
			lazy = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围沒有往下傳遞的纍加任務
			change = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围有没有更新操作的任务
			update = new boolean[MAXN << 2]; // 用来支持脑补概念中,某一个范围更新任务,更新成了什么
		}
java
		private void pushUp(int rt) {
            // 本位置累加和等于 左孩子和右孩子的和
			sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
		}
java
		// 在初始化阶段,先把sum数组,填好
		// 在arr[l~r]范围上,去build,1~N,
		// rt : 这个范围在sum中的下标
        // build(1 , N , 1) 根节点的位置是1 不过需要左孩子和右孩子的值
		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);    //(1 , n/2 , 2)
			build(mid + 1, r, rt << 1 | 1);// (n/2 , n , 3)
			pushUp(rt);
		}

add ​

java
		// 之前的,所有懒增加,和懒更新,从父范围,发给左右两个子范围
		// 分发策略是什么
		// rt 父节点的下标 ln表示左子树元素结点个数,rn表示右子树结点个数
		private void pushDown(int rt, int ln, int rn) {
			if (update[rt]) {  // 父节点更新信息为true
				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;
                // 给左右孩子发送后 变为0
				lazy[rt] = 0;
			}
		}

        // L~R, C 任务!(大范围 题目要求)
		// 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);  // 这个范围都加C 累加和*C
				lazy[rt] += C;   // 全包需要懒更新
				return;
			}
			// 任务没有把你全包!
			// l  r  mid = (l+r)/2
			int 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);
		}

update ​

java
 		// L~R  所有的值变成C
		// l~r  rt
		public 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);
		}

query ​

java
		// 1~6 累加和是多少? 1~8 rt (2 , 6  , 1 , 8 , 1)
		public 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;
			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;
		}

线段树例题 ​

题目 ​

java
俄罗斯方块  不需要累加和  但是要max(左树的最大值 和 右树的最大值)

代码 ​

java
	public static class SegmentTree {
		private int[] max;
		private int[] change;
		private boolean[] update;

		public SegmentTree(int size) {
			int N = size + 1;
			max = new int[N << 2];

			change = new int[N << 2];
			update = new boolean[N << 2];
		}

		private void pushUp(int rt) {
			max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]);
		}

		// 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];
				max[rt << 1] = change[rt];
				max[rt << 1 | 1] = change[rt];
				update[rt] = false;
			}
		}

		public 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;
				max[rt] = C;
				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);
		}

		public int query(int L, int R, int l, int r, int rt) {
			if (L <= l && r <= R) {
				return max[rt];
			}
			int mid = (l + r) >> 1;
			pushDown(rt, mid - l + 1, r - mid);
			int left = 0;
			int right = 0;
			if (L <= mid) {
				left = query(L, R, l, mid, rt << 1);
			}
			if (R > mid) {
				right = query(L, R, mid + 1, r, rt << 1 | 1);
			}
			return Math.max(left, right);
		}
	}