更多文章可以在本人的个人小站:https://kaiserwilheim.github.io 查看。
转载请注明出处。
思想
顾名思义,扫描线就是要模拟一根线,扫过整个图形。至于方向什么的自己根据喜好,这里选用从左往右的方式。
过程
朴素做法
首先我们看一下这个例子:
我们有三个矩形,分别用红、蓝、绿三色标注了出来。
他们相交的面积用其他的颜色标了出来。
我们所得到的信息只有矩形的左下端点和右上端点的坐标。也就是我们标红的这几个点:
然后我们可以画出一根线来,扫过这个多边形的所有面积。
这根线从最左边的边开始,每遇到一条边,就停下来计算之前扫到的面积,即为距上一条边的距离与扫描线落在图形上的长度之积,再加到之前的和上面。
用动画来做就是这样的:
但是这样做有一处需要注意的地方: 我们怎么才能知道每一次遇到一条边之后扫描线落在图像上的长度是多少 ?
如果我们每一次都去枚举、去找,我们程序的时间复杂度就达不到我们的要求了。
这就是扫描线算法的精髓之处。
线段树
分段
我们把 y y y 轴上的一段区间按照每一个存在的矩形的边的高度值分一下块:
我们只需要计算每一段所对应的在 x x x 轴上的长度就行了。
我们可以使用线段树这一强大的数据结构来帮助我们更快地进行区间修改。
标记
我们在遇到某个矩形的一条边的时候,我们需要看一下这两个东西:
- 它的长度;
- 他是起始边还是终止边。
如果是起始边的话,我们把他这条边所包含的所有区块都打上一个标记;如果是终止边的话,我们就把之前起始边打上的标记去掉。这样,只要有标记就是存在,不管有多少个;没有标记就是没有被覆盖,忽略不计。
所以,我们的线段树节点需要存储这个区段被标记的次数。同时,我们还需要存储基本的线段树信息,还有它的长度。
同时,我们用来存储矩形边的位置我们也需要改一下,使之能够存储这条边的上端点和下端点,还要标记这条边是起始边还是终止边。
最终我们使用动画模拟一下就是这个样子的:
代码
上代码。
#include<bits/stdc++.h>
using namespace std;
const int N = 1000010;
int n, cnt = 0;
long long lf, rt, tp, bt, a[N << 1];
struct ScanLine
{
long long l, r, h;
int mark;