扫描线详解
扫描线详解
写在前面
阅读本文的一些前置知识
1. 线段树
2. 离散化
首先你要保证你会上面两种算法,下面我默认你们在会这两种算法的基础上讲解。
线段树可以查看我的洛谷博客,正在更新。
而离散化下面我会再提一次,所以其实你不会离散化也没关系。
一些话
笔者写下这篇文章,并不是盈利性的目的,而是为了更多人能了解扫描线这一种算法。
尽管我写的可能不如别人好,但是我一定尽力写好了这一篇文章。
前置知识补充
线段树
我并不打算展开这一部分的知识,具体内容可以查看我的博客。
离散化
笔者并没有专门写一篇关于离散化的博客,因此我在这详细讲解这一部分的内容。
什么是离散化?
离散化就是一个小数据映射到大数据的过程。
需要离散化一般有这样的数据提示信息:\(n≤10^5,1≤x_i≤10^18\),而你要做的是维护\(1-n\)这样一个序列的信息。
很明显如果以 \(x\) 作为下标进行储存是不可以的,当然 \(STL\) 为你提供了一种映射方式就是:\(map\)。
但是很显然 \(map\) 常数很大,并且不是我们讨论的重点。
假设没有 \(map\),那么上面的问题将要怎么解决?
于是离散化横空出世。
为什么进行离散化?
上面已经说了,\(10^18\)的数组是开不了的,但是我们注意到这里的 \(n\) 非常的小(别想起状压好嘛,我在讲线段树!)
所以我们需要在 \(1-n\) 这个域上进行映射,那么原来每一个 \(x_i\) 就被投影到了 \(1-n\) 上,再利用 \(1-n\) 去维护即可。
这样我们就达到了维护的目的,还可以开的下数组,是不是很美妙?
如何进行离散化?
我们只需要一个方法建立起这个映射就可以了。
很显然不能乱建
然后我们考虑每一个数特有的属性。假设数字是不重复的,那么这个属性就是数字的大小!
所以我们可以根据大小建立映射。
首先,我们把要映射的数据投入一个数组,然后进行排序(至于排序的原因等会讲)。
如果数据可重但是你不需要维护数据信息而只是为了映射(映射当然可以多个到一个啦),那么去个重。
(当然如果所有的 \(x_i\) 都不相同也就不需要了)
而 \(STL\) 中为我们内置了一个去重的函数: \(unique\)。这个函数的使用要求就是数组必须是有序的。
所以我们需要排序。并且注意,这个去重函数是有返回值的,它返回不重复的元素后一位指针。
如果你想知道去重后还有几个数,只需要这么做:
int m = unique(Hash + 1, Hash + n + 1) - (Hash + 1);
因为 \(Hash\) 数组下标我从 \(1\) 开始,所以最后是减去\(Hahs + 1\),记住即可。
还有一点要注意:去重后的数组原来的数并没有消失,而是移到了数组的后端!
接下来你只需要每个数的大小,你可以通过 \(STL\) 中的 \(lower\_bound\) 实现。(返回的大于等于 \(x\) 的第一个数的指针)
然后你就知道了每个数的大小,所以就完成了离散化。
上面已经解决了不维护的数据或者是不重复的数据。
那么如果数据要维护并且会重复呢?
解决方案很简单,你只需要在一个数映射到的值域已经有值了后,在数组最末端插入这个数即可。
排序要注意把原来序列打乱,或者不使用 \(sort\),而是 \(stable\_sort\)。
具体的代码实现就是这样的:(如果上面没看懂看看代码吧)
long long Hash[N], a[N];
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
long long x;
scanf("%lld", &x);
Hash[i] = a[i] = x;
}
sort(Hash + 1, Hash + n + 1);
int m = unique(Hash + 1, Hash + n + 1) - (Hash + 1);
for(int i = 1; i <= n; i++) {
a[i] = lower_bound(Hash + 1, Hash + m + 1, a[i]) - Hash;
printf("%d ", a[i]);
}
上面的代码可以处理不重复数据或者不需要维护信息只需要知道值域的数据。
其实离散化之后并不需要你维护什么信息。(毕竟离散化是为了从复杂到简单的过程)
下面这份代码就是可重数据映射。
(其实离散化真的不需要维护什么序列的,序列的维护一般使用线段树)
long long Hash[N], a[N];
bool v[N];
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
long long x;
scanf("%lld", &x);
Hash[i] = a[i] = x;
}
sort(Hash + 1, Hash + n + 1);
int m = unique(Hash + 1, Hash + n + 1) - (Hash + 1), p = m;
for(int i = 1; i <= n; i++) {
int x = lower_bound(Hash + 1, Hash + m + 1, a[i]) - Hash;
if(v[x]) a[i] = ++p;
else a[i] = x, v[x] = true;
printf("%d ", a[i]);
}
上面那份代码你可以完成下面这题模板题:(其实很水的)
(还在出,出完放链接)
上面是离散化的大概内容。重点记忆的是第一份代码,接下来会用到。
扫描线
一个问题
现在给你几个矩形,请你求出它们的面积并/周长并。
我们有做过一维的区间覆盖问题(也就是贪心算法),但现在这个问题变成了二维,我们如何处理?
首先看一张图:
解决方案
我们在初中应该都做过一些类似求平面图形的面积的问题。我们可能会遇到一些不规则的图形,那让我们来思考一下,那个时候你们是如何解决这样的问题的呢?
我们一般会把那个图形分割成几块简单图形,然后一块一块求面积。
那么回到这个题目,我们是否也可以按照这样的思路把这几个矩形分割开来呢?
那当然是可以的。(巨佬:干嘛不打容斥)
容斥固然可以解决这样的题目,但是略显麻烦。我们考虑如何拆分矩形。
按照一些往常的经验,利用“差分”的思想,我们可以把矩形的始边看成 \(1\),终边看成 \(-1\)。
也就是这样: