算法--扫描线

news2024/12/23 9:22:56
写在前面:
这个算法理解还是挺好理解的,就是到后面解决面积并问题的时候开始难理解了,看了半天,主要是还有其他的知识没理解就开始搞这个了。虽然最后还是直接懂了。

文章目录

  • 扫描线算法的介绍
  • 一维问题
    • LintCode 391 数飞机
      • 题目描述
      • 解决
    • letcode 218. 天际线问题
      • 题目描述
      • 题解
  • 二维问题
    • 洛谷 P5490 【模板】扫描线
      • 题目描述
      • 题目分析

扫描线算法的介绍

扫描线顾名思义也就是一条线进行扫描,一般从左到右,从下到上也是可以的。

算法适用于,前后时间的问题,一维区间重合的问题,然后还有最难的二维矩形面积重合问题。
总的来说就类似于需要一条线冲起来一样的。

一维问题

LintCode 391 数飞机

题目描述

给出飞机的起飞和降落时间的列表,用序列 interval 表示. 请计算出天上同时最多有多少架飞机?
样例
样例 1:

输入: [(1, 10), (2, 3), (5, 8), (4, 7)]
输出: 3
解释: 
第一架飞机在1时刻起飞, 10时刻降落.
第二架飞机在2时刻起飞, 3时刻降落.
第三架飞机在5时刻起飞, 8时刻降落.
第四架飞机在4时刻起飞, 7时刻降落.
在5时刻到6时刻之间, 天空中有三架飞机.

样例 2:

输入: [(1, 2), (2, 3), (3, 4)]
输出: 1
解释: 降落优先于起飞.

解决

以时间为线,扫描时刻。
我们可以以起飞设置为1.
落地设置为-1.
但是需要注意的是落地有优先权

public int countOfAirplanes(List<Interval> airplanes) {
        // write your code here
        int[][] arr = new int[airplanes.size() * 2][2];
        for (int i = 0; i < airplanes.size(); i++) {
            arr[2 * i] = new int[]{airplanes.get(i).start, 1};
            arr[2 * i + 1] = new int[]{airplanes.get(i).end, -1};
        }
        Arrays.sort(arr, (o1, o2) -> o1[0] == o2[0] ? o1[1] - o2[1] : o1[0] - o2[0]);
        int ans = 0,count = 0;
        for (int[] ints : arr) {
            if (ints[1] == 1)
                count++;
            else
                count--;
            ans = Math.max(ans,count);
        }
        return ans;
    }

letcode 218. 天际线问题

题目描述

在这里插入图片描述
在这里插入图片描述

题解

这里提供的代码不是特别快。但是写起来舒服点。
还是老样子进行排序,然后入的高度设置为正,出设置为负。
我们每次只需要最高处的高度就可以了。所以用到pq。
pq的删除是一个线性 o ( n ) o(n) o(n) 的删除,需要的时间比较长。

 public List<List<Integer>> getSkyline(int[][] buildings) {
        ArrayList<Point> a = new ArrayList<>(buildings.length * 2 + 2);
        for (int[] building : buildings) {
            a.add(new Point(building[0], building[2]));
            a.add(new Point(building[1], -building[2]));
        }
        a.sort((o1, o2) -> o1.x == o2.x ? o2.h - o1.h : o1.x - o2.x);
        PriorityQueue<Integer> h = new PriorityQueue<>(Comparator.comparing(Integer::intValue).reversed());
        h.add(0);
        ArrayList<List<Integer>> ans = new ArrayList<>();
        int height = 0;
        for (Point point : a) {
            if (point.h > 0) {
                h.offer(point.h);
            } else {
                h.remove(-point.h);
            }
            if (h.peek() != height) {
                ans.add(Arrays.asList(point.x, h.peek()));
            }
            height = h.peek();
        }
        return ans;
    }

    record Point(int x, int h) {}

二维问题

这个问题比较一维问题的难点就在于,除了对x的记录外,我们还需要y的记录。一维的最后一个题目上,y其实不需要记录下面,因为下一定是0.
而二维的矩阵上下y都需要记录,需要得到长度。
现在我们从一道模板题来分析

好像知道线段树会好理解,我搞完在去学。

洛谷 P5490 【模板】扫描线

题目描述

n n n 个四边平行于坐标轴的矩形的面积并。
输入格式
第一行一个正整数 n n n

接下来 n n n 行每行四个非负整数 x 1 , y 1 , x 2 , y 2 x_1, y_1, x_2, y_2 x1,y1,x2,y2,表示一个矩形的四个端点坐标为 ( x 1 , y 1 ) , ( x 1 , y 2 ) , ( x 2 , y 2 ) , ( x 2 , y 1 ) (x_1, y_1),(x_1, y_2),(x_2, y_2),(x_2, y_1) (x1,y1),(x1,y2),(x2,y2),(x2,y1)

输出格式

一行一个正整数,表示 n n n 个矩形的并集覆盖的总面积。

样例 #1

样例输入 #1

2
100 100 200 200
150 150 250 255

样例输出 #1

18000

提示

对于 20 % 20\% 20% 的数据, 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 10 5 1 \le n \le {10}^5 1n105 0 ≤ x 1 < x 2 ≤ 10 9 0 \le x_1 < x_2 \le {10}^9 0x1<x2109 0 ≤ y 1 < y 2 ≤ 10 9 0 \le y_1 < y_2 \le {10}^9 0y1<y2109

题目分析

这算是扫描线的一个经常算的题目了,计算矩形的面积。
对于下面这个图来说
在这里插入图片描述
计算面积可以是
( 4 − 1 ) ∗ ( 3 − 1 ) + ( 6 − 3 ) ∗ ( 4.5 − 2 ) − ( 4 − 3 ) ∗ ( 3 − 2 ) (4-1)*(3-1)+(6-3)*(4.5-2) - (4-3)*(3-2) (41)(31)+(63)(4.52)(43)(32)
我们也可以拆分来计算
拆成abc3个部分来计算
面积就是
( 3 − 1 ) ∗ ( 3 − 1 ) + ( 4 − 3 ) ∗ ( 4.5 − 1 ) + ( 6 − 4 ) ∗ ( 4.5 − 2 ) (3-1)*(3-1)+(4-3)*(4.5-1)+(6-4)*(4.5-2) (31)(31)+(43)(4.51)+(64)(4.52)

在这里插入图片描述

我们就可以像一维那样,从左到右,分为入边和出边来计算。
△ x \triangle x x很好处理。但是高度就比较难处理了。

首先我想到的就是可以入边则加区间,出边则将区间减去。
每次都对区间来在进行一次区间计算高度。
但是这样复杂度比较高。

我们采取的是离散化来进行计算。
将y离散化,即下面这样的区间。

如果在区间则,入边则令所在区间+1,出边则-1
在这里插入图片描述
这个题的y离散后就是
[ 1 , 2 , 3 , 4.5 ] [1,2,3,4.5] [1,2,3,4.5]
进行模拟:

  • 第一条入边为[1,3] 长度为2,宽度为2 s u m = 4 sum = 4 sum=4
  • 第二条入边为[2,4] 长度为3.5 宽度为1 s u m = 7.5 sum = 7.5 sum=7.5
  • 第三条出边为[1,3] 则剩下[3,4] 长度为2.5 宽度为2 s u m = 11.5 sum = 11.5 sum=11.5

接下来就是实现了:
采用的是建树。
至于原因,我的理解是更快的求得长度。
如我想要获得 [ 1 , 3 ] [1,3] [13]就直接有想要 [ 2 , 4 ] [2,4] [24]就用 [ 1 , 4 ] [1,4] [14]减去 12 12 12
在这里插入图片描述

接下来就是代码了
先实现建树
树维护着

  • 树的节点个数
  • 访问的节点
  • 每个节点对应的区间
  • 区间所占用的长度

去重手写会更快,不过stream写的很爽。
本来还用set试试了,但是转化为Integer[]还行int[]太麻烦了。不如stream爽。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.Arrays;

public class test {

    static final StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }

    public static void main(String[] args) throws IOException {
        //初始化
        int n = nextInt();
        int size = n << 1;
        Line[] lines = new Line[size + 1];
        int[] yPos = new int[size];
//        HashSet<Integer> yPos = new HashSet<>();
        for (int i = 1; i <= n; i++) {
            int x1 = nextInt();
            int y1 = nextInt();
            int x2 = nextInt();
            int y2 = nextInt();
            int now = i << 1;
            int next = now - 1;
            lines[now] = new Line(y1, y2, x1, 1);
            lines[next] = new Line(y1, y2, x2, -1);
            yPos[now - 1] = y1;
            yPos[next - 1] = y2;
        }


        // 离散化排序
        yPos = Arrays.stream(yPos).distinct().sorted().toArray();
        int len = yPos.length;
        uniqueY = new int[len + 2];
        System.arraycopy(yPos, 0, uniqueY, 1, len);
        uniqueY[len + 1] = Integer.MAX_VALUE;
        Arrays.sort(lines, 1, size + 1);
        uniqueYCnt = len;
//        duplicateRemoval(yPos, size);

        // 线段树
        SegmentTree tree = new SegmentTree(uniqueYCnt, uniqueY);

        // 扫描线
        long ans = 0;
        for (int i = 1; i < size; i++) {
            Line line = lines[i];
            tree.update(line.y1, line.y2, line.weight);
            ans += tree.getSum() * (lines[i + 1].x - line.x);
        }

        System.out.println(ans);
    }

	//去重后的个数
    static int uniqueYCnt;
    //去重数组
    static int[] uniqueY;


    // 手写去重会更快
//    static void duplicateRemoval(int[] arr, int len) {
//        int[] array = Arrays.stream(arr).distinct().toArray();
//        int last = uniqueY[1] = arr[1];
//        uniqueYCnt = 1;
//        for (int i = 2; i <= len; i++) {
//            if (arr[i] == last)
//                continue;
//            last = uniqueY[++uniqueYCnt] = arr[i];
//        }
//        uniqueY[uniqueYCnt + 1] = Integer.MAX_VALUE;
//    }

    static class Line implements Comparable<Line> {
        int y1;
        int y2;
        int x;
        int weight;

        public Line(int y1, int y2, int x, int weight) {
            this.y1 = y1;
            this.y2 = y2;
            this.x = x;
            this.weight = weight;
        }

        @Override
        public int compareTo(Line o) {
            return x - o.x;
        }
    }

}

class SegmentTree {
    private final long[] tree;// 线段树
    private final int size;// 离散化后的长度
    private final int[] mapping;// 离散化
    private final int[] visitCount;// 访问次数

    // 初始化
    public SegmentTree(int size, int[] mapping) {
        this.size = size;
        this.mapping = mapping;
        this.tree = new long[size * 10 + 1];
        this.visitCount = new int[size * 10 + 1];
    }

    // 获取区间左边
    private int updateL;
    // 获取区间右边
    private int updateR;

    /**
     * 获取区间和
     *
     * @param l     区间左边
     * @param r     区间右边
     * @param value 访问更新值 1 为访问 -1 为取消访问
     */
    public void update(int l, int r, int value) {
        updateL = l;
        updateR = r;
        getUpdate(1, size, 1, value);
    }

    /**
     * 更新
     *
     * @param currentL 当前区间左边
     * @param currentR 当前区间右边
     * @param pos      当前节点
     * @param value    访问更新值 1 为访问 -1 为取消访问
     */
    private void getUpdate(int currentL, int currentR, int pos, int value) {
        int leftValueMapping = mapping[currentL];
        int rightValueMapping = mapping[currentR + 1];
        // 不相交

        if (rightValueMapping <= updateL || leftValueMapping >= updateR) {
            return;
        }
        // 完全覆盖
        if (updateL <= leftValueMapping && rightValueMapping <= updateR) {
            visitCount[pos] += value;
        } else {
            // 递归子节点
            int mid = (currentL + currentR) >> 1;
            // 左子节点
            getUpdate(currentL, mid, pos << 1, value);
            // 右子节点
            getUpdate(mid + 1, currentR, pos << 1 | 1, value);
        }
        if (visitCount[pos] > 0) {
            // 访问次数大于0,说明这个区间被访问过
            // 那么这个区间的值就是区间长度
            tree[pos] = rightValueMapping - leftValueMapping;
        } else {
            int temp = pos << 1;
            // 没有访问=子节点相加
            tree[pos] = tree[temp] + tree[temp | 1];
        }
    }

    // 获取区间和
    public long getSum() {
        return tree[1];
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/504963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【历史上的今天】5 月 9 日:中国黄页上线;Red Hat 创始人出生;Scratch 2.0 发布

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 5 月 9 日&#xff0c;在 1993 年的今天&#xff0c;第一届东亚运动会在上海隆重开幕&#xff0c;这是亚洲体育运动史上的新篇章。来自东亚地区的中国、日本、…

【笔记】【HTTP】《图解HTTP》第5章 与HTTP协做的Web服务器

前言 有输入就要有产出&#xff0c;该笔记是本人看完《图解HTTP》后对每章涉及到的知识进行汇总博客将会已书的每章为一篇发布&#xff0c;下一篇博客发布时间不确定笔记中有些个人理解后整理的笔记&#xff0c;可能有所偏差&#xff0c;也恳请读者帮忙指出&#xff0c;谢谢。…

信号signal编程测试

信号会打断系统调用&#xff0c;慎用&#xff0c;就是用的时候测一测。 下面是信号的基础测试 信号 信号&#xff08;signal&#xff09;机制是UNIX系统中最为古老的进程之间的通信机制。它用于在一个或多个进程之间传递异步信号。信号可以由各种异步事件产生&#xff0c;例如…

数据结构与算法1:引入概念

接下来系统的学一下数据结构与算法的知识&#xff0c;本章节是第一部分&#xff1a;数据结构与算法的进入与基本概述 第一章&#xff1a;引入概念 【铁打的算法demo】先来看到题&#xff1a; 如果 a b c 1000&#xff0c;且 a2 b2 c2&#xff08;a, b , c 为⾃然数&…

快进来,带你了解FPGA基础知识---lattice莱迪斯深力科MachXO2 FPGA系列简介

FPGA基础知识---lattice莱迪斯深力科MachXO2 LCMXO2-4000HC-4TG144I FPGA简介 FPGA基础知识&#xff1a;FPGA是英文Field&#xff0d;Programmable Gate Array的缩写&#xff0c;即现场可编程门阵列&#xff0c;它是在PAL、GAL、CPLD等可编程器件的基础上进一步发展的产物。它…

《程序员面试金典(第6版)》面试题 16.17. 连续数列(贪心算法思想,动态规划算法思想,C++)

题目描述 给定一个整数数组&#xff0c;找出总和最大的连续数列&#xff0c;并返回总和。 示例&#xff1a; 输入&#xff1a; [-2,1,-3,4,-1,2,1,-5,4] 输出&#xff1a; 6 解释&#xff1a; 连续子数组 [4,-1,2,1] 的和最大&#xff0c;为 6。进阶&#xff1a; 如果你已经实…

elementUI tabs切换 echarts宽度挤压到一起 由100%变成100px

被压缩的图表&#xff1a; 正常显示 <el-tabs v-model"activeName" type"card" tab-click"handleClick"><el-tab-pane name"first"></el-tab-pane><el-tab-pane name"second" label"未达成原因…

如何在Kali Linux中获得root权限?

根用户名或账户在Linux或任何其他类似Unix的操作系统中拥有所有可用命令和文件的默认权限。它也被称为超级用户、根账户和根用户。用户名&#xff1a;"kali "是登录新Kali系统的标准凭证。这建立了一个用户 "kali "的会话&#xff0c;你必须在 "Sudo …

Java—JDK8新特性—方法引用【内含思维导图】

目录 4.方法引用 思维导图 4.1 什么是方法引用 4.2 为什么要使用方法引用 4.3 方法引用语法 4.4 方法引用的5种情况使用示例 4.方法引用 思维导图 4.1 什么是方法引用 方法引用就是Lambda表达式&#xff0c;也就是函数式接口的一个实例&#xff0c;通过方法的名称来指向一…

Word怎么分页,提高效率就靠这3种方法!

案例&#xff1a;Word怎么分页 【文档要进行分页处理&#xff0c;但是我尝试了好多次还是不行&#xff01;大家知道Word怎么分页吗&#xff1f;】 在使用Microsoft Word处理文档时&#xff0c;我们常常需要进行分页操作。Word的分页功能可以将文档分成多个页面&#xff0c;以…

【Vue3 插件篇】GSAP 动画库与 图片预览插件

GSAP 动画库 GSAP&#xff08;GreenSock Animation Platform&#xff09;是一个专业的动画库&#xff0c;可以用它完成你想要的各种效果 官网地址&#xff1a;https://greensock.com/ 参考文章一&#xff1a;https://www.jianshu.com/p/a8e150f0e569 参考文章二&#xff1a…

利用MQ事务消息实现分布式事务

MQ事务消息使用场景 消息队列中的“事务”&#xff0c;主要解决的是消息生产者和消息消费者的数据一致性问题。 拿我们熟悉的电商来举个例子。一般来说&#xff0c;用户在电商 APP 上购物时&#xff0c;先把商品加到购物车里&#xff0c;然后几件商品一起下单&#xff0c;最后…

为游客提供完美旅程:携程集团携手亚马逊云科技联合创新

刚刚过去的“五一”假期&#xff0c;旅游行业展现出了强劲的复苏势头。经文化和旅游部数据中心测算&#xff0c;全国国内旅游出游合计2.74亿人次&#xff0c;同比增长70.83%。 然而&#xff0c;出行前的航班高铁订票、酒店商旅预订、出游行程安排&#xff0c;就已经让不少家庭为…

C++linux高并发服务器项目实践 day9

Clinux高并发服务器项目实践 day9 信号集信号集相关函数以下信号集相关的函数都是对自定义的信号集进行操作sighandler_t函数sigaction函数 SIGCHLD信号共享内存共享内存使用步骤共享内存操作函数 信号集 许多信号相关的系统调用都需要能表示一组不同的信号&#xff0c;多个信…

深度思考:在 AI 时代,你会被放大一千倍的能力是什么?

Datawhale干货 作者&#xff1a;艾芙&#xff0c;复旦大学&#xff0c;百姓AI教育负责人 前言 大家晚上好&#xff0c;我是艾芙&#xff0c;百姓 AI 的 AI 教育负责人。 先做一下自我介绍&#xff0c;我是一个在技术圈和教育圈反复横跳的斜杠中年了。大约在 5 年前&#xff0c…

C++格式输入输出

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;数据结构 &#x1f525;座右铭&#xff1a;“不要等到什么都没…

常用的数据中心部署架构

说起数据中心&#xff0c;相信大家并不陌生。随着互联网行业的蓬勃发展&#xff0c;大数据时代的快速到来&#xff0c;数据中心同我们的生活紧密联系&#xff0c;息息相关。我们日常生活中的各种数据几乎都存储在数据中心里&#xff0c;例如&#xff1a;手机照片的云端备份、放…

Python小姿势 - # Python相关技术知识点

Python相关技术知识点 标题 在Python中如何处理文件 如果你要处理文件&#xff0c;那么在Python中你需要使用到os模块中的一些方法。 首先&#xff0c;你需要使用os.path.exists方法来判断文件是否存在&#xff1a; python if os.path.exists(file.txt): print(文件存在) else:…

asp.net企业员工考勤管理系统

企业员工管理系统主要是为企业内部管理员工使用的&#xff0c;主要功能分为员工和管理员两部分&#xff0c;主要的功能有用户登录&#xff0c;管理员信息管理&#xff0c;公告信息管理&#xff0c;文件审批管理&#xff0c;员工信息管理&#xff0c;工资信息管理&#xff0c;奖…

全景丨0基础学习VR全景制作,平台篇第19章:热点功能-文本

大家好&#xff0c;欢迎观看蛙色VR官方——后台使用系列课程&#xff01; 功能说明 应用场景 热点&#xff0c;指在全景作品中添加各种类型图标的按钮&#xff0c;引导用户通过按钮产生更多的交互&#xff0c;增加用户的多元化体验。 文本热点&#xff0c;即点击热点后会弹出…