Algorithm training camp [7] line segment tree

Line segment tree, IndexTree

Segment tree:

1. A data structure that supports range-wide modification and range-wide query

2. The scope of the problem to be solved:

Large-scale information can be processed only from the information on the left and right sides,

Instead of traversing the specific conditions of the left and right sub-ranges

Segment Tree Example

Given an array arr, the user wants you to implement the following three methods

1) void add(int L, int R, int V) : add V to each number on the array arr[L…R]

2) void update(int L, int R, int V): Let each number on the array arr[L…R] become V

3) int sum(int L, int R): Let return the cumulative sum of the whole range of arr[L...R]

How to make these three methods have a time complexity of O(logN)?

Violent methods slightly. . .

The requirement for time complexity is high, in fact, it is to find a way to exchange space for time.

Use an array instead of a binary tree to store these elements.

It is equivalent to storing the full binary tree in an array.

The cumulative sum of the line segment tree is stored in the form of data, and the data length is 4 times that of the original array

Why 4 times?

Find a way to make the binary tree constructed by all numbers a full binary tree, that is, add 0 when it is not enough to a certain power of 2, without affecting the overall result

When the number is exactly 2^n +1the number of , the redundant space of the array is the most, which happens to be the case where the left half satisfies the full binary tree, and the right half just has one more number.
insert image description here
For the layer where the leaf node is located, the left half is a n-1 number , and the right half is a number.
For the left side, it is almost the same Nsize, and the right side also needs to add Na space.
From the root node to the bottom layer, the upper layer needs 2*n - 1 element space
, almost 4*Na space needs to be prepared in total

When the number of elements is 2^nexactly , the
bottom layer needs element space, and the upper layer from the root node to the bottom needs element space . At this time, there are 2*n spaces that can hold all the elements.nn - 1

Therefore, the overall allocation 4*nof space can meet the element space requirements in the worst case.

in the
The 0 node is not used, the left child of the i node is 2 * i , and the right child is 2 * i+1
sum (i) = sum ( 2 * i) + sum ( 2 * i+1)

insert image description here

the code

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;
 
		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];
			}
      // 4倍空间的数组
      // 用来支持脑补概念中,某一个范围的累加和信息
			sum = new int[MAXN << 2]; 
      // 用来支持脑补概念中,某一个范围沒有往下傳遞的纍加任務
			lazy = new int[MAXN << 2]; 
      // 用来支持脑补概念中,某一个范围有没有更新操作的任务
			change = new int[MAXN << 2]; 
      // 用来支持脑补概念中,某一个范围更新任务,更新成了什么
			update = new boolean[MAXN << 2]; 
		}
		// 之前的,所有懒增加,和懒更新,从父范围,发给左右两个子范围
		// 分发策略是什么
		// 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;
				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;
			//  2 * i  等同于 rt<<1
			// 2*i + 1 等同于 rt<<1 | 1 
			build(l, mid, rt << 1);
			build(mid + 1, r, rt << 1 | 1);
      // 左半边和右半边都填好之后再累加
			pushUp(rt);
		}
   	// private 对内开放。对外封闭
		private void pushUp(int rt) {
    
    
      // rt << 1 | 1.  即 rt *2 + 1 
			sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
		}
		
		// L~R  所有的值变成C
		// l~r  rt
		public void update(int L, int R, int C, int l, int r, int rt) {
    
    
       // 更新操作之前,将所有lazy 清空,因为已经不需要他们做操作了
     
			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);
			}
      // 更新 sum 数组
			pushUp(rt);
		}
 
		// 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);
				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);
		}
 
		// 1~6 累加和是多少? 1~8 rt
		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;
		}
	}

Topic two

Imagine a standard Tetris game, the X axis is the axis on which the blocks eventually fall to the bottom
Here is a simplified version of the game:
1) Only square blocks will fall
2) [a,b] -> represents a square block with side length b , the left edge of the block falls from above along the line X = a
3) It is considered that the entire X axis may catch the block, that is to say, the simplified version of the game has no overall left and right boundaries
4) There is no overall left and right boundaries, so The simplified version of the game does not remove the blocks, since none of the levels are filled.
Given a N*2 two-dimensional array matrix, which can represent N blocks falling one by one,
return the maximum height after each fall.
Leetcode topic: https://leetcode.com/problems/falling-squares/

class Solution {
    
    
	private int N;
	private int[] max;
	private Integer[] update;

	public List<Integer> fallingSquares(int[][] positions) {
    
    
		HashMap<Integer, Integer> map = index(positions);
		N = map.size();
//		N = this.arraySize(positions) + 1;// range()数组最大数
    // 线段树长度 == 数组长度 * 4
		int m = N << 2; 
		max = new int[m];// 最大值缓存结构
		update = new Integer[m]; // 更新缓存结构
		int start = 1; // 线段树最小索引
		int root = 1; // 线段树的根index
		List<Integer> ans = new ArrayList<>(); // 答案
		int max = 0; // 当前最大高度
		for (int i = 0; i < positions.length; i++) {
    
    
			int[] position = positions[i]; // 当前掉落的方块
			int L = map.get(position[0]); // 当前掉落左边界
			int R = map.get(position[0] + position[1] - 1); // 当前掉落右边界
//			int L = position[0]; // 当前掉落左边界
//			int R = position[0] + position[1] - 1; // 当前掉落右边界
			int height = this.query(L, R, start, N, root) + position[1]; // 掉落前这一段的最大高度 + 新增高度 == 这一段掉落后高度
			max = Math.max(max, height);// 掉落后的最大高度
			ans.add(max);
			this.update(L, R, height, start, N, root); // [L,R] 上都变成 height (下面有空的也没事)
		}
		return ans;
	}

	private void update(int L, int R, int H, int l, int r, int root) {
    
    
		if (L <= l && r <= R) {
    
    
			this.update[root] = H;// 当前范围属于更新范围内, 由root代表
			this.max[root] = H;
			return;
		}
		int mid = (l + r) >> 1;
		this.push(root);
		if (L <= mid) {
    
    
			this.update(L, R, H, l, mid, root << 1); 
		}
		if (R > mid) {
    
    
			this.update(L, R, H, mid + 1, r, (root << 1) | 1);
		}
		// 当前点最大值
		max[root] = Math.max(max[root << 1], max[(root << 1) | 1]);
	}

	private void push(int root) {
    
    
		if (this.update[root] != null) {
    
    
			int update = this.update[root];
			this.update[root << 1] = update;// 左子树
			this.update[(root << 1) | 1] = update;// 右子树
			this.max[root << 1] = update;
			this.max[(root << 1) | 1] = update;
			this.update[root] = null;
		}
	}

	private Integer query(int L, int R, int l, int r, int root) {
    
    
		if (L <= l && r <= R) {
    
    
			return this.max[root];
		}
		int mid = (l + r) >> 1;
		this.push(root);
		int maxLeft = 0;
		int maxRight = 0;
		if (L <= mid) {
    
    
			maxLeft = this.query(L, R, l, mid, root << 1);
		}
		if (R > mid) {
    
    
			maxRight = this.query(L, R, mid + 1, r, (root << 1) | 1);
		}
		return Math.max(maxLeft, maxRight);
	}

	private int arraySize(int[][] positions) {
    
    
		int maxNumber = 0;
		for (int[] position : positions) {
    
    
			maxNumber = Math.max(position[0] + position[1], maxNumber);
		}
		return maxNumber + 1; // 数组长度
	}
//离散化处理
	public HashMap<Integer, Integer> index(int[][] positions) {
    
    
		TreeSet<Integer> pos = new TreeSet<>();
		for (int[] arr : positions) {
    
    
			pos.add(arr[0]);
			pos.add(arr[0] + arr[1] - 1);
		}
		HashMap<Integer, Integer> map = new HashMap<>();
		int count = 0;
		for (Integer index : pos) {
    
    
			map.put(index, ++count);
		}
		return map;
	}
}

作者:wa-pian-d
链接:https://leetcode-cn.com/problems/falling-squares/solution/wa-pian-699-diao-luo-de-fang-kuai-java-z-ljxp/
来源:力扣(LeetCode
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

IndexTree lightweight line segment tree

Features:

1) Support interval query

2) It is not as strong as a line segment tree, but it is very easy to change to a one-dimensional, two-dimensional, or three-dimensional structure

3) Only support single-point update

The line segment tree can be realized, but using indexTree is better than the line segment tree

indexTree is not suitable for solving the problem of uniformly changing from L...R to a value, it will be very slow

But indexTree is very fast in processing single-point updates, and can also quickly query the cumulative sum of a range after processing single-point updates

solved problem:

indexTree can only solve the problem of how to maintain a structure and quickly query a cumulative and fast problem after a single point is updated

The help array of indexTree: 1-1 2-1,2 3-3 4-1,2,3,4 ...

The binary form of index in the help array is 010111000

managed the following values

Start with the first number that splits the last 1 ----> to the number itself

// 下标从1开始!
	public static class IndexTree {
    
    
 
		private int[] tree;
		private int N;
 
		// 0位置弃而不用!
		public IndexTree(int size) {
    
    
			N = size;
			tree = new int[N + 1];
		}
 
		// 1~index 累加和是多少?
		public int sum(int index) {
    
    
			int ret = 0;
			while (index > 0) {
    
    
				ret += tree[index];
				index -= index & -index;
			}
			return ret;
		}
 
		// index & -index : 提取出index最右侧的1出来
		// index :           0011001000
		// index & -index :  0000001000
		public void add(int index, int d) {
    
    
			while (index <= N) {
    
    
				tree[index] += d;
				index += index & -index;
			}
		}
	}	

Find the maximum number of coincident line segments

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;

public class Code01_CoverMax {
    
    

	public static int maxCover1(int[][] lines) {
    
    
		int min = Integer.MAX_VALUE;
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < lines.length; i++) {
    
    
			min = Math.min(min, lines[i][0]);
			max = Math.max(max, lines[i][1]);
		}
		int cover = 0;
		for (double p = min + 0.5; p < max; p += 1) {
    
    
			int cur = 0;
			for (int i = 0; i < lines.length; i++) {
    
    
				if (lines[i][0] < p && lines[i][1] > p) {
    
    
					cur++;
				}
			}
			cover = Math.max(cover, cur);
		}
		return cover;
	}

	public static int maxCover2(int[][] m) {
    
    
		Line[] lines = new Line[m.length];
		for (int i = 0; i < m.length; i++) {
    
    
			lines[i] = new Line(m[i][0], m[i][1]);
		}
		Arrays.sort(lines, new StartComparator());
		PriorityQueue<Line> heap = new PriorityQueue<>(new EndComparator());
		int max = 0;
		for (int i = 0; i < lines.length; i++) {
    
    
			while (!heap.isEmpty() && heap.peek().end <= lines[i].start) {
    
    
				heap.poll();
			}
			heap.add(lines[i]);
			max = Math.max(max, heap.size());
		}
		return max;
	}

	public static class Line {
    
    
		public int start;
		public int end;

		public Line(int s, int e) {
    
    
			start = s;
			end = e;
		}
	}

	public static class StartComparator implements Comparator<Line> {
    
    

		@Override
		public int compare(Line o1, Line o2) {
    
    
			return o1.start - o2.start;
		}

	}

	public static class EndComparator implements Comparator<Line> {
    
    

		@Override
		public int compare(Line o1, Line o2) {
    
    
			return o1.end - o2.end;
		}

	}

	// for test
	public static int[][] generateLines(int N, int L, int R) {
    
    
		int size = (int) (Math.random() * N) + 1;
		int[][] ans = new int[size][2];
		for (int i = 0; i < size; i++) {
    
    
			int a = L + (int) (Math.random() * (R - L + 1));
			int b = L + (int) (Math.random() * (R - L + 1));
			if (a == b) {
    
    
				b = a + 1;
			}
			ans[i][0] = Math.min(a, b);
			ans[i][1] = Math.max(a, b);
		}
		return ans;
	}

	public static void main(String[] args) {
    
    
		System.out.println("test begin");
		int N = 100;
		int L = 0;
		int R = 200;
		int testTimes = 200000;
		for (int i = 0; i < testTimes; i++) {
    
    
			int[][] lines = generateLines(N, L, R);
			int ans1 = maxCover1(lines);
			int ans2 = maxCover2(lines);
			if (ans1 != ans2) {
    
    
				System.out.println("Oops!");
			}
		}
		System.out.println("test end");
	}

}

Find the number of rectangles that cover the area of ​​the largest rectangle in the plane

package class07;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;

public class Code04_CoverMax {
    
    

	public static class Rectangle {
    
    
		public int up;
		public int down;
		public int left;
		public int right;

		public Rectangle(int up, int down, int left, int right) {
    
    
			this.up = up;
			this.down = down;
			this.left = left;
			this.right = right;
		}

	}

	public static class DownComparator implements Comparator<Rectangle> {
    
    
		@Override
		public int compare(Rectangle o1, Rectangle o2) {
    
    
			return o1.down != o2.down ? (o1.down - o2.down) : o1.toString().compareTo(o2.toString());
		}
	}

	public static class LeftComparator implements Comparator<Rectangle> {
    
    
		@Override
		public int compare(Rectangle o1, Rectangle o2) {
    
    
			return o1.left != o2.left ? (o1.left - o2.left) : o1.toString().compareTo(o2.toString());
		}
	}

	public static class RightComparator implements Comparator<Rectangle> {
    
    
		@Override
		public int compare(Rectangle o1, Rectangle o2) {
    
    
			return o1.right != o2.right ? (o1.right - o2.right) : o1.toString().compareTo(o2.toString());
		}
	}

	// 矩形数量是N
	// O(N*LogN)
	// +
	// O(N) * [ O(N) + O(N *LogN) ]
	public static int maxCover(Rectangle[] recs) {
    
    
		if (recs == null || recs.length == 0) {
    
    
			return 0;
		}
		// 根据down(底)排序
		Arrays.sort(recs, new DownComparator());
		// 可能会对当前底边的公共局域,产生影响的矩形
		// list -> treeSet(有序表表达)
		TreeSet<Rectangle> leftOrdered = new TreeSet<>(new LeftComparator());
		int ans = 0;
		// O(N)
		for (int i = 0; i < recs.length;) {
    
     // 依次考察每一个矩形的底边
			// 同样底边的矩形一批处理
			do {
    
    
				leftOrdered.add(recs[i++]);
			} while (i < recs.length && recs[i].down == recs[i - 1].down);
			// 清除顶<=当前底的矩形
			removeLowerOnCurDown(leftOrdered, recs[i - 1].down);
			// 维持了右边界排序的容器
			TreeSet<Rectangle> rightOrdered = new TreeSet<>(new RightComparator());
			for (Rectangle rec : leftOrdered) {
    
     // O(N)
				removeLeftOnCurLeft(rightOrdered, rec.left);
				rightOrdered.add(rec);// O(logN)
				ans = Math.max(ans, rightOrdered.size());
			}
		}
		return ans;
	}

	public static void removeLowerOnCurDown(TreeSet<Rectangle> set, int curDown) {
    
    
		List<Rectangle> removes = new ArrayList<>();
		for (Rectangle rec : set) {
    
    
			if (rec.up <= curDown) {
    
    
				removes.add(rec);
			}
		}
		for (Rectangle rec : removes) {
    
    
			set.remove(rec);
		}
	}

	public static void removeLeftOnCurLeft(TreeSet<Rectangle> rightOrdered, int curLeft) {
    
    
		List<Rectangle> removes = new ArrayList<>();
		for (Rectangle rec : rightOrdered) {
    
    
			if (rec.right > curLeft) {
    
    
				break;
			}
			removes.add(rec);
		}
		for (Rectangle rec : removes) {
    
    
			rightOrdered.remove(rec);
		}
	}

}

2D area and search

package class32;

// 测试链接:https://leetcode.com/problems/range-sum-query-2d-mutable
// 但这个题是付费题目
// 提交时把类名、构造函数名从Code02_IndexTree2D改成NumMatrix
public class Code02_IndexTree2D {
    
    
	private int[][] tree;
	private int[][] nums;
	private int N;
	private int M;

	public Code02_IndexTree2D(int[][] matrix) {
    
    
		if (matrix.length == 0 || matrix[0].length == 0) {
    
    
			return;
		}
		N = matrix.length;
		M = matrix[0].length;
		tree = new int[N + 1][M + 1];
		nums = new int[N][M];
		for (int i = 0; i < N; i++) {
    
    
			for (int j = 0; j < M; j++) {
    
    
				update(i, j, matrix[i][j]);
			}
		}
	}

	private int sum(int row, int col) {
    
    
		int sum = 0;
		for (int i = row + 1; i > 0; i -= i & (-i)) {
    
    
			for (int j = col + 1; j > 0; j -= j & (-j)) {
    
    
				sum += tree[i][j];
			}
		}
		return sum;
	}

	public void update(int row, int col, int val) {
    
    
		if (N == 0 || M == 0) {
    
    
			return;
		}
		int add = val - nums[row][col];
		nums[row][col] = val;
		for (int i = row + 1; i <= N; i += i & (-i)) {
    
    
			for (int j = col + 1; j <= M; j += j & (-j)) {
    
    
				tree[i][j] += add;
			}
		}
	}

	public int sumRegion(int row1, int col1, int row2, int col2) {
    
    
		if (N == 0 || M == 0) {
    
    
			return 0;
		}
		return sum(row2, col2) + sum(row1 - 1, col1 - 1) - sum(row1 - 1, col2) - sum(row2, col1 - 1);
	}

}

Reference 1

Reference 2

Reference 3

source code

Guess you like

Origin blog.csdn.net/qq_41852212/article/details/124453106