ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

算法与数据结构进阶课第七节笔记

2021-05-26 22:01:59  阅读:202  来源: 互联网

标签:rt arr 进阶 int 线段 mid 第七节 数据结构 public


线段树

1,一种支持范围整体修改和范围整体查询的数据结构

2,解决的问题范畴:
大范围信息可以只由左、右两侧信息加工出,
而不必遍历左右两个子范围的具体状况

给定一个数组arr,用户希望你实现如下三个方法
1)void add(int L, int R, int V) :  让数组arr[L…R]上每个数都加上V
2)void update(int L, int R, int V) :  让数组arr[L…R]上每个数都变成V    
3)int sum(int L, int R) :让返回arr[L…R]这个范围整体的累加和
怎么让这三个方法,时间复杂度都是O(logN)

因为需要扩充为满二叉树,N个数需要准备4N的空间,虽然浪费一些空间,但是绝对够用

懒更新机制,在更新时只下发到任务完全包住的位置,不直接下发到底层,在新任务到时,下发一层原本的值,自己变0,然后看新任务能不能包住下一层

         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];
			}
			sum = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围的累加和信息
			lazy = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围沒有往下傳遞的纍加任務
			change = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围有没有更新操作的任务
			update = new boolean[MAXN << 2]; // 用来支持脑补概念中,某一个范围更新任务,更新成了什么
		}

		private void pushUp(int rt) {
			sum[rt] = sum[rt << 1] + sum[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];
				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;
			}
			// 更新还没进行下发,更新后又来了一些add任务,所以需要在更新下发后,继续下发攒着的add任务
			if (lazy[rt] != 0) {
				// rt给,左孩子,右孩子收到下发的任务,懒住
				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的任务清0,因为已经下发,实际值在他自己懒住时也已经累加
				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);
			build(mid + 1, r, rt << 1 | 1);
			pushUp(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);
		}

		// L..R -> 任务范围 ,所有的值累加上C
		// 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  没有把本身表达范围 l,r 彻底包住
			int mid = (l + r) >> 1;
			// 下发之前所有攒的任务
			pushDown(rt, mid - l + 1, r - mid);
			// 左孩子是否需要接到任务
			if (L <= mid) {
				add(L, R, C, l, mid, rt << 1);
			}
			// 右孩子是否需要接到任务
			if (R > mid) {
				add(L, R, C, mid + 1, r, rt << 1 | 1);
			}
			// 左右孩子做完任务后,我更新我的sum信息
			pushUp(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;
		}

	}

例:想象一下标准的俄罗斯方块游戏,X轴是积木最终下落到底的轴线
下面是这个游戏的简化版:
1)只会下落正方形积木
2)[a,b] -> 代表一个边长为b的正方形积木,积木左边缘沿着X = a这条线从上方掉落
3)认为整个X轴都可能接住积木,也就是说简化版游戏是没有整体的左右边界的
4)没有整体的左右边界,所以简化版游戏不会消除积木,因为不会有哪一层被填满。

给定一个N*2的二维数组matrix,可以代表N个积木依次掉落,
返回每一次掉落之后的最大高度

掉落的方块

[a,b] 的宽度是a+b,那么我们用线段树的 [a,a+b-1] 来表示,因为如果要用[a,b] 来表示,[b,c] 就落不下来了,因为 arr[b] 已经有值了

     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);
		}

	}

	/**
	 * [2,7] -> 2,8
	 * [3,10] -> 3,12
	 * 离散化后
	 * 实际坐标 :2,3,8,12
	 * 对应坐标: 1,2,3,4
	 * @param positions
	 * @return
	 */
	public HashMap<Integer, Integer> index(int[][] positions) {
		// 离散化坐标,将实际的坐标值计算出来,再从小到大认为他是1,2,3这样的下标
		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;
	}

	public List<Integer> fallingSquares(int[][] positions) {
		HashMap<Integer, Integer> map = index(positions);
		int N = map.size();
		SegmentTree segmentTree = new SegmentTree(N);
		int max = 0;
		List<Integer> res = new ArrayList<>();
		// 每落一个正方形,收集一下,所有东西组成的图像,最高高度是什么
		for (int[] arr : positions) {
			int L = map.get(arr[0]);
			int R = map.get(arr[0] + arr[1] - 1); // 首先处理了块的右边界,所以左插,右插都没问题
			int height = segmentTree.query(L, R, 1, N, 1) + arr[1];
			max = Math.max(max, height);
			res.add(max);
			segmentTree.update(L, R, height, 1, N, 1);
		}
		return res;
	}

想要查询的父值,能由左孩子和右孩子提供,就能使用线段树,累加和,最大最小值

例2:

有N个房子,另有17种颜色,想要实现[L,R] 范围上刷成一种颜色,还想查询[L,R] 上不同的颜色又多少种。

使用一个整数的二进制位表示刷上去的颜色

父上的颜色数量就是左孩子或右孩子

例3:

最大线段重合问题,有一组线段,看看重合的最多次是多少

和房子的高度一样,也要处理宽度[a,b]为[a,a+b-1],使用线段树

更容易的方法:

[1,10] [1,7] [2,5] [4,6]

1、首先用线段开始的问题排序

2、使用一个小根堆,首先看[1,10],堆里比小于等于1的数字都扔出,10入堆,此时堆的大小是1

[1,7] 小于等于1的扔出,7放入,那么堆顶就是7,堆大小就是2

如果处理的线段是[L,R] 就弹出所有小于等于L的数,加入R后,小根堆里有几个数就是要的答案

处理n个线段,记录每次的最大值,最后就是最大的重合次数

小根堆里存的是,之前的线段能对现在的产生影响,这些线段的结尾都存在堆里

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

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

        public int getStart(){
            return start;
        }

        public int getEnd(){
            return end;
        }
    }

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

    }

例4:

有很多矩形,怎么看最多的盖住次数

1、矩形遍历顺序 底 从小到大

2、重合区域的底,必然是某个矩形的底,把每个底都都变成直线,求必须以这个底为底的重合区域数量

3、将高不高于这个底的矩阵全部扔掉,留下来的矩形的宽看成一个线段,转换为线段重合问题

   public static int coverMax1(Rectangle[] resc){
        if (resc == null || resc.length == 0){
            return 0;
        }
        Arrays.sort(resc,Comparator.comparing(Rectangle::getDown));
        // 可能回对当前底边的公共区域,产生影响的矩阵
        TreeSet<Rectangle> leftOrdered = new TreeSet<>(Comparator.comparing(Rectangle::getLeft));
        int ans = 0;
        for (int i = 0; i < resc.length; i++){
            int curDown = resc[i].getDown();
            int index = i;
            // 已经排好了序,所以不用担心越过某个矩形
            while (resc[index].down == curDown){
                leftOrdered.add(resc[index]);
                index ++;
            }
            i = index;
            // 将顶小于等于当前底的数去掉
            removeLowerOnCurDown(leftOrdered,curDown);
            TreeSet<Rectangle> rightOrdered = new TreeSet<>(Comparator.comparing(Rectangle::getRight));
            for (Rectangle rec : leftOrdered){
                removeLowerOnCurLeft(rightOrdered,rec.getLeft());
                rightOrdered.add(rec);
                ans = Math.max(ans,rightOrdered.size());
            }

        }
        return ans;
    }

    public static void removeLowerOnCurDown(TreeSet<Rectangle> set, int curDown){
        Iterator<Rectangle> iterator = set.iterator();
        while (iterator.hasNext()){
            Rectangle next = iterator.next();
            if (next.up <= curDown){
                set.remove(next);
            }
        }
    }

    public static void removeLowerOnCurLeft(TreeSet<Rectangle> set, int curLeft){
        Iterator<Rectangle> iterator = set.iterator();
        while (iterator.hasNext()){
            Rectangle next = iterator.next();
            if (next.right <= curLeft){
                set.remove(next);
            }
        }
    }

 

标签:rt,arr,进阶,int,线段,mid,第七节,数据结构,public
来源: https://blog.csdn.net/yshuoo/article/details/117265418

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有