扫描线
扫描线法是一种求矩形面积并/周长并的好方法。
扫描线:假设有一条扫描线从一个图形的下方扫向上方(或者左方扫到右方),那么通过分析扫描线被图形截得的线段就能获得所要的结果。该过程可以用线段树进行加速。
面积的求法其实可以简化为 ∑ \sum ∑截线段长度 × \times ×扫过的高度。这也是扫描线算法最基础的应用。
问题在于如何才能模拟扫描线从下向上扫过图形,并且快速计算出当前扫描线被截得的长度。
现在假设,扫描线每次会在碰到横边的时候停下来(基本思路),如图。
简单来说,可对图形面积产生影响的元素,也就是这些横边左右端点的坐标。
为了快速计算出截线段长度,可以将横边赋上不同的权值,具体为:对于一个矩形,其下边权值为 1 1 1,上边权值为 − 1 -1 −1。
然后把所有的横边按照 y y y坐标升序排序。这样,对于每个矩形,扫描线总是会先碰到下边,然后再碰到上边。那么就能保证扫描线所截的长度永远非负了。
这样操作以后,就可以和线段树扯上关系。先把所有端点在 x x x轴上离散化(其实就是把所有点的横坐标存到 X [ ] X[] X[]里,然后升序排个序,最后去重)。
在这个例子中,四个端点的纵投影总共把x轴切割成了五个部分。取中间的三部分线段,建立一棵线段树,线段树上每个点位维护一条线段(也就是一个区间)的信息:
1.该线段被覆盖了多少次(被多少个矩形所覆盖)。
2.该线段内被整个图形所截的长度是多少。
显然,只要一条线段被覆盖,那么它肯定被图形所截。所以,整个问题就转化为了一个区间查询问题,即:每次将 当前扫描线扫到的边 对应的信息 按照之前赋上的权值更新,然后再查询线段树根节点的信息,最后得到当前扫描线扫过的面积。这就可以用线段树来实现了(毕竟人家叫“线段”树嘛)。
下面我们来直观感受一下这个过程,图中绿色部分表示已计算的面积。
我们已经知道,这棵线段树的每个节点都对应了一条线段。考虑将线段树上节点对应的区间和横边建立映射关系。先看对于一个叶子节点 p p p,建树时保证了 t r e e [ p ] . l = t r e e [ p ] . r tree[p].l=tree[p].r tree[p].l=tree[p].r,但其保存的信息很显然不可能只是某条线段的一个端点。(如果一条线段的两个端点重合,那么它实质上仅是一个点)。再看一个节点的左右儿子,同样地,建树的时候已经保证了左右儿子的区间不会重合(交集为空),但是看这样两条相邻线段: