SP17123 解题报告

news/2024/11/16 15:57:00/文章来源:https://www.cnblogs.com/Brilliant11001/p/18415616

题目传送门

扫描线是一种求矩形面积并或周长并的好方法。

假设在一个平面上有几个矩形,要求它们共覆盖了多大的面积。由于矩形可能会有重叠的地方,所以最后要求的图形就是一个不规则的图形。

要求它的面积十分复杂,特别是在矩形数量很大时。为了解决这个问题,扫描线法应运而生。

想象一下,有一根看不见的直线从下到上扫过这个平面。在扫描的过程中,直线上的一些线段会被给定的矩形覆盖。如果我们将这些覆盖的线段长度进行积分,就可以得到矩形的面积之和。

如图所示:

图是我从 OI WiKi 上偷的。

这时候就有一个疑问了:这玩意儿和线段树有什么关系呢?

先别慌,我们慢慢分析。

由图可知:直线上被并集图形覆盖的长度只会在每个矩形的上下边界处发生变化。换言之,整个并集图形可以被分成 \(N \times 2\) 段,每一段在直线上覆盖的长度(记为 \(L\))是固定的,因此该段的面积就是 \(L\times\) 该段的宽度,各段面积之和即为所求。

为了快速计算出截线段长度,可以将横边赋上不同的权值,具体为:对于一个矩形,其下边权值为 \(1\),上边权值为 \(-1\)

然后把所有的横边按照 \(y\) 坐标升序排序。这样,对于每个矩形,扫描线总是会先碰到下边,然后再碰到上边。那么就能保证扫描线所截的长度永远非负了。

我们维护一条扫描线,将这条线逐渐向上平移,遇到每一根横着的线就停下来,算算目前扫描线上被覆盖的长度和与上次停下相比走过了多少距离,两者相乘后累加到 ans 里。

然后再看目前遇到的这根线,如果是 \(+1\) 线就将它覆盖到扫描线上,否则就将它从扫描线中减去。

朴素的做法是维护一个数组 \(s\) 作为扫描线,用一个 \(len\) 维护目前扫描线上被覆盖的长度,\(s_{i}\) 表示扫描线上的这个坐标被覆盖了几次。

这样,每遇到一条 \(+1\) 线,就在 \(s\) 数组上进行朴素 \(+1\),反之 \(-1\),同时更新 \(len\)

这样做的时间复杂度最坏为 \(O(n^2)\)

考虑优化。

容易发现这个算法的瓶颈在于朴素的区间加和区间减,所以用线段树来维护。

建立一棵线段树,维护两个值:

  1. 该线段被覆盖的次数;

  2. 该线段被覆盖的长度。

那线段树该怎么建立?

细节一:

一般这种类型的题矩形的坐标要么就特别大,要么就可能是小数,这时候就需要进行离散化。一定要注意下标的转化!

值得注意,线段树中的叶子节点表示的区间 \([l, r]\) 其实不能直接用来表示元线段,因为 \(l = r\),表示的其实是长度为 \(0\) 的线段,所以我们需要对它进行一些调整。

考虑把线段树每个节点 \(u\) 对应的区间 \([l, r]\) 不变,改变区间和横边的映射关系,具体为:节点 \(u\) 对应 \((xs_{u}, xs_{u} + 1)\) 这条横边。

细节二:

虽然有区间修改操作,但是并不需要懒标记。

因为只有当该线段被覆盖的次数大于 \(0\) 时才会被用来更新答案,我们又只用询问根结点的值,所以只需要保证所有节点的父节点的信息是正确的,就能保证答案的正确性,而此时子结点的值就不需要用到了,所以不需要 \(\operatorname{pushdown}\)

那为什么是正确的呢?

我们维护了两个值:\(cnt\)\(len\),因为 \(+1\)\(-1\) 操作必定是成对出现的,而且先 \(+1\)\(-1\),所以 \(cnt\) 必定大于等于 \(0\),我们不妨对此进行分类论:

  1. \(cnt = 0\) 时,这时长度就用它的两个儿子来计算,只需要在修改完后 \(\operatorname{pushup}\) 一遍就行了,这时候有没有 \(\operatorname{pushdown}\) 都没有关系。

  2. \(cnt > 0\) 时,此区间完全被覆盖,则 \(len = r - l + 1\),这时再给这个区间加上一个数,若加完后 \(cnt > 0\),则完全没有影响,因为我们只统计 \(cnt\) 大于 \(0\) 的长度,所以长度还是 \(r - l + 1\);若加完后 \(cnt = 0\),则同上。

综上所述,不需要 \(\operatorname{pushdown}\) 操作。

\(\texttt{Code:}\)

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;const int N = 100010;
typedef long long ll;
struct Segment{int y, x1, x2;int val;bool operator <(const Segment &o) const {return y < o.y;}
}seg[N << 1];struct node{int l, r;//线段树的节点tr[u]表示的线段树Node区间[tr[u].l,tr[u].r]维护离散化后的区间 --> [y_l, y_r + 1]int cnt;int len;#define l(x) tr[x].l#define r(x) tr[x].r#define cnt(x) tr[x].cnt#define len(x) tr[x].len
}tr[N << 4];vector<int> xs;
int n;inline int ls(int p) {return p << 1;}
inline int rs(int p) {return p << 1 | 1;}int find(int x) { //返回x在vector中储存的下标 return lower_bound(xs.begin(), xs.end(), x) - xs.begin();
}void pushup(int p) {if(cnt(p)) len(p) = xs[r(p) + 1] - xs[l(p)]; //若全部覆盖,被覆盖的长度就是区间长度 else if(l(p) != r(p)) {// 如果tr[u].cnt等于0其实有两种情况:// 1. 完全覆盖. 这种情况由modify的第一个if进入. //    这时下面其实等价于把"由完整的l, r段贡献给len的部分清除掉", //    而留下其他可能存在的子区间段对len的贡献// 2. 不完全覆盖, 这种情况由modify的else最后一行进入. //    表示区间并不是完全被覆盖,可能有部分被覆盖,所以要通过儿子的信息来更新len(p) = len(ls(p)) + len(rs(p));}else len(p) = 0; //表示为叶子节点且该线段没被覆盖,为无用线段,长度变为0
}void build(int p, int l, int r) {l(p) = l, r(p) = r;if(l != r) {int mid = l + r >> 1;build(ls(p), l, mid);build(rs(p), mid + 1, r);}
}void modify(int p, int l, int r, int val) { //表示从线段树中l点到r点的出现次数 + valif(l <= l(p) && r >= r(p)) {cnt(p) += val;pushup(p); //更新该节点的lenreturn ;}int mid = l(p) + r(p) >> 1;if(l <= mid) modify(ls(p), l, r, val);if(r > mid) modify(rs(p), l, r, val);pushup(p);
}int main() {scanf("%d", &n);int x1, x2, y1, y2;for(int i = 0, j = 0; i < n; i++) {scanf("%d%d%d%d", &x1, &y1, &x2, &y2);seg[j++] = {y1, x1, x2, 1};seg[j++] = {y2, x1, x2, -1};xs.push_back(x1);xs.push_back(x2);}sort(seg, seg + n * 2); //将横着的线段按照y坐标从小到大排序 sort(xs.begin(), xs.end());xs.erase(unique(xs.begin(), xs.end()), xs.end()); //离散化去重 //离散化后纵坐标有2n个点, 2n-1个区间,构建线段树,线段树的节点维护这些区间tr[i] --> [y_i, y_i+1],所以线段树的节点个数与区间个数相同2n-1build(1, 0, xs.size() - 2); //共有xs.size() - 1个y点位,就会构成xs.size() - 2条线段 ll res = 0;for(int i = 0; i < n * 2; i++) {if(i > 0) res += 1ll * tr[1].len * (seg[i].y - seg[i - 1].y);modify(1, find(seg[i].x1), find(seg[i].x2) - 1, seg[i].val); //更新 //这里一定要把原区间 变换到 线段树表示的区间 //线段树的节点 维护 离散化后的区间:tr[u] --> [tr[u].l,tr[u].r] --> [xs_l, xs_r + 1]//原区间: seg[i].x1 ~ seg[i].x2//离散化后的区间: find(seg[i].x1) ~ find(seg[i].x2)//线段树中的区间: find(seg[i].x1) ~ find(seg[i].x2) - 1}printf("%lld\n", res);return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Codeforces Round 970 (Div. 3) 复盘

感觉还是差点火候啊,做div3都有点吃力Codeforces Round 970 (Div. 3) Sep/01/2024 22:35UTC+8 length 02:15 好闲啊,还要写 div3 的复盘,就当听歌的同时练习翻译兼打字了。总而言之还是太菜了# Who = Penalty * A B C D E F G H1624 BaSEc1d 6 250+00:04 +00:19 +00:24 +00:…

2024年7连测第二场

A link如果想要\(x_1+y_2=x_2+y_1\),就是\(x_1-x_2=y_1-y_2\)即可,那么我们可以存一下每一个\(i\)的\(x\)与\(y\)的差,每到一个\(i\)就看一下前面有几个的差和它相等,这一个就可以和多少个组上对。点击查看代码 #include<bits/stdc++.h>using namespace std;int n,an…

AI老照片修复神器,Anole下载介绍

最近AI老照片修复上色,再一次火出圈,一些社交平台关于此话题内容流量满满,尤其是在小红书和抖音火的不得了,本期文章就来给大家分享下AI修复老照片的方式方法 本文主要介绍使用Anole修复老照片的方法,只需输入一张黑白或彩色照片,即可得到修复后的彩色结果,让往日的老照…

肖健飞的第一次作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/zjlg/rjjc这个作业的目标 进行自我评估,熟悉博客园的使用方法,阐述自己的课程期望姓名-学号 肖健飞-2022329301124一、自我介绍 (一)基本情况及爱好 我叫肖健飞,来自江苏泰州,是信息科学与工程学院22级自动化(2)班…

2024ICPC网络赛第一场题解(部分)

2024ICPC网络赛第一场题解和参考代码以及部分思路说明这一场基本纯挂件,给队友翻译翻译题面,帮队友打打板子了,可惜最后40sL题冲了一个 \(O(\frac{n^3}{w})\) 的bitset最后wa了,所以下面的题解我也只能看着队友代码说说大概,主要参考一下代码吧。 A 题意 给出32个队伍的能…

sign与unsigned的原理、数据存储与硬件的关系

目录关键字unsigned和signed数据在计算机中的存储原码 与 补码的转化与硬件关系原,反,补的原理:整型存储的本质变量存取的过程类型目前的作用十进制与二进制快速转换大小端字节序判断当前机器的字节序"负零"(-128)的理解截断建议在无符号类型的数值后带上u, 关键字un…

C++中对象的延迟构造

本文并不讨论“延迟初始化”或者是“懒加载的单例”那样的东西,本文要讨论的是分配某一类型所需的空间后不对类型进行构造(即对象的lifetime没有开始),更通俗点说,就是跳过对象的构造函数执行。 使用场景 我们知道,不管是定义某个类型的对象还是用operator new申请内存,…

黑客失误?76.2万车主,家庭住址信息泄露

​据Cybernews研究团队发现,一个包含76.2W名车主及其车辆详细信息的敏感数据库已经在网上泄露。这些数据托管在一个美国的IP地址上,首次发现是在8月4日,至少暴露了48小时。据该团队称,泄露的数据揭示了车主的敏感信息。泄露的细节几乎揭示了拥有车辆的个人的所有信息,包括…

C++ 在 Visual Studio 如何将指针星号设置成靠近变量而不是类型

“工具”->“选项”->“文本编辑器”->“C/C++”->“代码样式”->“格式设置”->“间距”->“指针/引用对齐方式”->“右对齐”。

Git冲突解决技巧

在多人协作的软件开发项目中,Git 冲突是不可避免的现象。当两个或更多的开发者同时修改了同一段代码,并且尝试将这些修改合并到一起时,冲突就发生了。解决这些冲突是确保代码库健康和项目顺利进行的关键。在多人协作的软件开发项目中,Git 冲突是不可避免的现象。当两个或更…

AI写作助手哪些好用?6款强大的AI写作助手值得收藏!

在内容创作日益重要的今天,AI写作助手已经成为许多创作者的得力工具。它们不仅能够提高写作效率,还能在一定程度上保证文章质量。面对市场上琳琅满目的AI写作助手,如何选择一款好用且适合自己的工具呢?以下推荐6款强大的AI写作助手,它们各具优势,助力创作者轻松应对各种写…