扫描线总结

news/2024/11/16 13:46:40/文章来源:https://www.cnblogs.com/laoshan-plus/p/18377346

扫描线是线段树的典型应用。这玩意不难,用途也比较狭窄,重点在理解思想。


例 0 【模板】扫描线

题意

\(n\) 个四边平行于坐标轴的矩形的面积并。

对于 \(100\%\) 的数据,\(1 \le n \le {10}^5\)\(0 \le x_1 < x_2 \le {10}^9\)\(0 \le y_1 < y_2 \le {10}^9\)

解析

如果用暴力法求,先单独求出每个矩形的面积,然后把所有矩形的面积加起来减去任意两个矩形的交集,时间复杂度为 \(O(n^2)\),无法通过本题。

下面用新的方法求,也就是我们要讲的 扫描线

现在假设我们有一根线,从下往上开始扫描:

如图所示,我们可以把整个矩形分成如图各个颜色不同的小矩形,那么这个小矩形的高就是我们扫过的距离,只需相邻矩形纵坐标相减即可求出。那么就只剩下了一个变量,那就是矩形的长一直在变化。

我们为了维护矩形的长,我们给每一个矩形的上下边进行标记,定义下面的边为 “入边”,上面的边为 “出边”。入边标记为 \(1\),出边标记为 \(-1\)。每遇到一个入边,证明进入了一个矩形,每遇到一个出边,就证明离开了一个矩形。利用 \(1\)\(-1\) 可以轻松达到这种状态。

那么这个算法如何用线段树实现?如图,我们将矩形的宽划分为了一段段小宽,遇到假如现在遇到一条入边 \([x_1,x_2]\),我们就相当于给 \([x_1,x_2]\) 这个区间 \(+1\),遇到出边则同理 \(-1\)相当于是区间和

所以我们对读入的所有按照纵坐标从小到大进行排序,然后一个个扫描,最后答案加上线段树根节点的值 * 矩形高度。时间复杂度 \(O(n\log n)\)

坑点

  • 每个矩形有一个入边和一个出边,所以存储边的数组要开 2 倍
  • 可见线段树维护的值域等于矩形横坐标的值域,但本题值域迭勾个 \(10^9\),所以需要离散化
  • 离散化后值域依然在 \(2\times10^5\)(别忘了有 \(2n\) 条边),再加上线段树本身需要 4 倍空间,所以线段树数组要开 8 倍
  • 如果你只是这样它依然还是个炸,需要在 pushup 时特判 s == t 时就不要再合并左右儿子的答案了,因为它已经是叶子节点。或者,你也可以把数组再开大一点。
  • 别忘了开 long long。
  • 细节挺多的,一定一定要分清楚点和区间的关系,注意各种 +1-1。

实现

#include <bits/stdc++.h>
#define int long long
#define lp p << 1
#define rp p << 1 | 1
using namespace std;constexpr int MAXN = 1e5 + 5;
struct ScanLine {int y, lx, rx, io;ScanLine() {}ScanLine(int y, int lx, int rx, int io): y(y), lx(lx), rx(rx), io(io) {}friend bool operator < (ScanLine a, ScanLine b) {return a.y < b.y;}
} line[MAXN << 1];
int xx[MAXN << 1];
struct SegTree {int l, r;int tag, len;
} st[MAXN << 3];void build(int s, int t, int p) {st[p].l = s, st[p].r = t;if (s == t) return;int mid = (s + t) >> 1;build(s, mid, lp);build(mid + 1, t, rp);
}void pushup(int p) {int s = st[p].l, t = st[p].r;if (st[p].tag) st[p].len = xx[t + 1] - xx[s];else if (s == t) st[p].len = 0;else st[p].len = st[lp].len + st[rp].len;
}void update(int l, int r, int k, int p = 1) {int s = st[p].l, t = st[p].r;if (l <= s && t <= r) {st[p].tag += k;pushup(p);return;}int mid = (s + t) >> 1;if (l <= mid) update(l, r, k, lp);if (mid < r) update(l, r, k, rp);pushup(p);
}signed main() {int n, cnt = 0;int X1, X2, Y1, Y2, ans = 0;scanf("%lld", &n);while (n--) {scanf("%lld%lld%lld%lld", &X1, &Y1, &X2, &Y2);line[++cnt] = ScanLine(Y1, X1, X2, 1);xx[cnt] = X1;line[++cnt] = ScanLine(Y2, X1, X2, -1);xx[cnt] = X2;}sort(xx + 1, xx + cnt + 1);sort(line + 1, line + cnt + 1);int num = unique(xx + 1, xx + cnt + 1) - xx - 1;build(1, num, 1);for (int i = 1, l, r; i <= cnt; i++) {ans += st[1].len * (line[i].y - line[i - 1].y);l = lower_bound(xx + 1, xx + num + 1, line[i].lx) - xx;r = lower_bound(xx + 1, xx + num + 1, line[i].rx) - xx;update(l, r - 1, line[i].io);}cout << ans << '\n';return 0;
}

例 1 Atlantis

题意与例 0 相同,增加两点:坐标为小数、多测。

针对坐标为小数的情况,只需把所有和坐标有关的变量改为 double 类型即可。

针对多测,别忘清空。似乎结构体线段树在多测时建树很麻烦,换种写法就行。

例 2 [IOI1998] [USACO5.5] 矩形周长Picture

题意

给定 \(N\) 个矩形,求这些矩形的并集的周长。保证 \(1\le N<5000\),坐标值域 \(\left[-10^4,10^4\right]\)

解析

求矩形周长并是扫描线的又一典型应用。思路相仿,但是要复杂一些,有两种方法:

  1. 做两次扫描。易得周长 = 横线总长 + 竖线总长,所以跑两遍扫描线即可。这种方法码量较大,我没有采用。
  2. 做一次扫描。其实在计算横线的同时就可以计算竖线。

把所有横线按纵坐标从小到大排序,每扫描一条横线,都计算两种值:横线和竖线。

横线的计算方法和例 0 是一样的。每次扫描,新增横线的长度 = 当前小矩形长 - 上次小矩形长。

如何计算竖线?首先,一个矩形的一条入边会有两条竖线,出边不会产生竖线;其次,如果两个矩形相邻或重叠,那么也只会产生两条竖线,而不是四条。这些竖线的长度 = 下一条横线的高度 - 当前横线的高度。

总而言之,线段树中需维护更多信息:一是当前独立线段的条数,也就是不重合的竖线条数,记为 \(\textit{num}\);而是两个布尔变量 \(\textit{lbd},\textit{rbd}\),表示当前线段的左右端点是否是独立的,目的是为了维护 \(\textit{num}\)。理解了思想后,代码容易。

坑点

  • 这次的值域小,无需离散化,但开线段树的时候需要把握一下横坐标的最小值和最大值。
  • 周长不同于面积,相邻的矩形会对答案产生影响。所以在排序的时候,如果两条边纵坐标相同,入边应排在出边之前,否则 WA 80pts。

实现

#include <bits/stdc++.h>
#define int long long
#define lp p << 1
#define rp p << 1 | 1
#define INF (0x3f3f3f3f)
using namespace std;constexpr int MAXN = 5005;
struct ScanLine {int y, lx, rx, io;ScanLine() {}ScanLine(int y, int lx, int rx, int io): y(y), lx(lx), rx(rx), io(io) {}friend bool operator < (ScanLine a, ScanLine b) {if (a.y == b.y) return a.io > b.io; // 精辟 return a.y < b.y;}
} line[MAXN << 1];
struct SegTree {int l, r;bool lbd, rbd;int num, tag, len;
} st[MAXN << 4];void build(int s, int t, int p) {st[p].l = s, st[p].r = t;if (s == t) return;int mid = (s + t) >> 1;build(s, mid, lp);build(mid + 1, t, rp);
}void pushup(int p) {int s = st[p].l, t = st[p].r;if (st[p].tag) {st[p].lbd = st[p].rbd = 1;st[p].len = t - s + 1;st[p].num = 1;} else if (s == t) {st[p].lbd = st[p].rbd = st[p].len = st[p].num = 0;} else {st[p].lbd = st[lp].lbd;st[p].rbd = st[rp].rbd;st[p].len = st[lp].len + st[rp].len;st[p].num = st[lp].num + st[rp].num;if (st[rp].lbd && st[lp].rbd) --st[p].num;}
}void update(int l, int r, int k, int p = 1) {int s = st[p].l, t = st[p].r;if (l <= s && t <= r) {st[p].tag += k;pushup(p);return;}int mid = (s + t) >> 1;if (l <= mid) update(l, r, k, lp);if (mid < r) update(l, r, k, rp);pushup(p);
}signed main() {int n, cnt = 0, lbd = INF, rbd = -INF;int X1, Y1, X2, Y2;scanf("%lld", &n);for (int i = 1; i <= n; i++) {scanf("%lld%lld%lld%lld", &X1, &Y1, &X2, &Y2);lbd = min(lbd, X1);rbd = max(rbd, X2);line[++cnt] = ScanLine(Y1, X1, X2, 1);line[++cnt] = ScanLine(Y2, X1, X2, -1);}sort(line + 1, line + cnt + 1);int ans = 0, last = 0;build(lbd, rbd, 1);for (int i = 1; i <= cnt; i++) {if (line[i].lx < line[i].rx) update(line[i].lx, line[i].rx - 1, line[i].io);ans += st[1].num * 2 * (line[i + 1].y - line[i].y);ans += abs(st[1].len - last);last = st[1].len;}cout << ans << '\n';return 0;
}

例 3 窗口的星星

Fleeting time does not blur my memory of you. Can it really be 4 years since I first saw you? I still remember, vividly, on the beautiful Zhuhai Campus, 4 years ago, from the moment I saw you smile, as you were walking out of the classroom and turned your head back, with the soft sunset glow shining on your rosy cheek, I knew, I knew that I was already drunk on you. Then, after several months’ observation and prying, your grace and your wisdom, your attitude to life and your aspiration for future were all strongly impressed on my memory. You were the glamorous and sunny girl whom I always dream of to share the rest of my life with. Alas, actually you were far beyond my wildest dreams and I had no idea about how to bridge that gulf between you and me. So I schemed nothing but to wait, to wait for an appropriate opportunity. Till now — the arrival of graduation, I realize I am such an idiot that one should create the opportunity and seize it instead of just waiting.

These days, having parted with friends, roommates and classmates one after another, I still cannot believe the fact that after waving hands, these familiar faces will soon vanish from our life and become no more than a memory. I will move out from school tomorrow. And you are planning to fly far far away, to pursue your future and fulfill your dreams. Perhaps we will not meet each other any more if without fate and luck. So tonight, I was wandering around your dormitory building hoping to meet you there by chance. But contradictorily, your appearance must quicken my heartbeat and my clumsy tongue might be not able to belch out a word. I cannot remember how many times I have passed your dormitory building both in Zhuhai and Guangzhou, and each time aspired to see you appear in the balcony or your silhouette that cast on the window. I cannot remember how many times this idea comes to my mind: call her out to have dinner or at least a conversation. But each time, thinking of your excellence and my commonness, the predominance of timidity over courage drove me leave silently.

Graduation, means the end of life in university, the end of these glorious, romantic years. Your lovely smile which is my original incentive to work hard and this unrequited love will be both sealed as a memory in the deep of my heart and my mind. Graduation, also means a start of new life, a footprint on the way to bright prospect. I truly hope you will be happy everyday abroad and everything goes well. Meanwhile, I will try to get out from puerility and become more sophisticated. To pursue my own love and happiness here in reality will be my ideal I never desert.

Farewell, my princess!

If someday, somewhere, we have a chance to gather, even as gray-haired man and woman, at that time, I hope we can be good friends to share this memory proudly to relight the youthful and joyful emotions. If this chance never comes, I wish I were the stars in the sky and twinkling in your window, to bless you far away, as friends, to accompany you every night, sharing the sweet dreams or going through the nightmares together.

题意

天空中有很多星星(看作平面直角坐标系),已知每个星星的坐标和亮度(都是整数)。求用宽为 \(w\)、高为 \(h\) 的矩形(\(w,h\) 为正整数)能围住的星星的亮度总和最大是多少(矩形边界上的星星不算)。

解析

截原题的题目背景是有原因的。

将星星转化为矩形,问题转化为:平面上有若干个区域,每个区域都有一个权值,求在哪个坐标上重叠的区域权值和最大

但由于 “矩形边界上的星星不算”,为了处理边界,我们作以下假设:将所有星星的坐标向左、向下各移动 \(0.5\) 的距离,坐标从 \((x,y)\) 变为 \((x-0.5,y-0.5)\)。不妨设圈住星星的矩形顶点坐标都是整数,于是每个区域的左下角可看作 \((x,y)\),右上角可看作 \((x+w-1,y+h-1)\),边界也算在内。易证这些假设不会影响答案。

于是我们用扫描线算法,维护区间最大值即可。剩下的内容不是难点,看代码即可理解。

AC Code

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

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

相关文章

Pollard-Rho学习笔记

1.利用最大公约数求出一个约数 n和某个数的公约数一定是n的约数,即\(\forall k \in\mathbf{N}_{+},\gcd(k,n) \mid n\),只要选取适当的k使得\(1<\gcd(k,n)< n\),,就能够求得n的一个约数 满足这个条件的k很多,n的因数的大部分倍数都可行 我们通过\(f(x)=(x^2+c)\bmod…

DaVinci Resolve Studio 19.0 正式版 (macOS, Windows) - 剪辑、调色、特效和音频后期制作

DaVinci Resolve Studio 19.0 正式版 (macOS, Windows) - 剪辑、调色、特效和音频后期制作DaVinci Resolve Studio 19.0 正式版 (macOS, Windows) - 剪辑、调色、特效和音频后期制作 Blackmagic Design DaVinci Resolve Studio 请访问原文链接:https://sysin.org/blog/davinci…

读软件开发安全之道:概念、设计与实施07密码学(上)

密码学1. 加密工具 1.1. 加密工具之所以没有得到充分使用,就是因为人们往往认为密码学是一个准入门槛极高的专业领域 1.2. 如今的加密学大部分都源自纯数学,所以只要能够正确使用,加密学确实行之有效1.2.1. 不代表这些算法本身确实无法破解,而是需要数学领域出现重大突破才…

004.MinIO-DirectPV分布式存储部署

MinIO部署介绍 部署概述 Kubernetes hostpath、local和本地静态配置都存在需要事先在node节点准备好可用的块存储或文件系统,例如对插入的硬盘,或者磁盘阵列做分区格式化,文件系统则需提前创建好Kubernetes即将利用的挂载目录,并且两种方法都会有亲和性限制,无法做到让Kub…

dotnet 默认创建的 JsonContent 没有 Content Lenght 的内容头

本文记录一个 dotnet 的设计问题,默认创建出来的 JsonContent 对象的 Headers 里,是没有 Content-Length 信息的如下面代码创建一个 JsonContent 对象 using System.Net.Http.Json;var foo = new Foo();var jsonContent = JsonContent.Create(foo);class Foo {public int Val…

dotnet X11 多次调用 XPutImage 是否能做到渲染同步

本文将告诉大家我在麒麟系统和统信系统以及分别搭配飞腾和兆芯处理器的设备上,使用连续的 XPutImage 方法推送界面,测试是否能够在一次渲染内完成。测试结论是不能做到渲染同步本文的核心测试代码如下XPutImage(display, handle, gc, ref xImage, @event.ExposeEvent.x, @eve…

dotnet C# 结构体出方法弹栈之后的行为

本文记录我在 .NET 9 里测试的行为,在方法里面创建的在栈上的结构体,在方法执行结束之后,栈上的结构体将会被弹栈进入不受管理区域,此时的结构体内存内容不会立刻被清空或被改写这是我在对 dotnet X11 栈空间被回收导致调用 XPutShmImage 闪退 博客的内容进行更多的测试,确…

Tesla 开发者 API 指南:BLE 密钥 – 身份验证和车辆命令

注意:本工具只能运行于 mac 或者 linux, win下不支持。 1. 克隆项目到本地 https://github.com/teslamotors/vehicle-command.git 2. 项目根目录下执行命令 go get ./... go build ./... go install ./... cd cmd cd tesla-control go build 3. 生成密钥 生成私钥 openssl e…

it程序员常用的技术社区网站有哪些?

it程序员常用的技术社区网站有哪些??作为程序员,选择好合适的开发社区对提高自己的编程能力会有很大的帮助,技术人员经常会在各种技术交流社区游逛。优秀的实时开发社区确实能帮你积累不少开发经验1、gitHub是一个面向开源及私有软件项目的托管平台,因为只支持git作为唯一…

推荐7款美观且功能强大的WPF UI库

前言 经常看到有小伙伴在DotNetGuide技术社区交流群里提问:WPF有什么好用或者好看的UI组件库推荐的?,今天大姚给大家分享7款开源、美观、功能强大、简单易用的WPF UI组件库。 WPF介绍 WPF 是一个强大的桌面应用程序框架,用于构建具有丰富用户界面的 Windows 应用。它提供了灵…

HTML+JS初试水

情境 参加了培训的第二次课, 这里是第二颗的作业题, 及我的解答. 1、使用 html 写一个网页,要求满足以下条件: (1)网页中含有任意一张图片,图片路径使用绝对路径,鼠标悬停在图片时出现“马哥教育”文本,且点击图片可跳转至马哥教育官方页面 (2)网页中包含账号、密码登…

Vue3的学习---8

8. Vue可复用解决方案 Vue可复用方案是指在Vue.js项目中,通过创建可复用的组件、指令、插件等,来提高代码的复用性和可维护性常见的Vue可复用方案有:组件(Components)、指令(Directives)、插件(Plugins)、混入(Mixins)、高阶组件(Higher-Order Components, HOC)等…