怨念
这个专题其实不难,但是翻了一圈网上的博客,写得是云里雾里,我打算用一篇博客把它讲明白
前序知识
能看懂这篇文章需要: 线段树基础知识。 线段树染色问题基本概念。 * 离散化操作
目标
首先这个扫描线算法解决的是什么问题? * 主要解决的是ACM中的 * 矩形面积问题 * 矩形周长问题 * 多边形面积问题
这篇博客主讲的问题是:
- 求矩形面积并 HDU 1542
题目分析
这道题最主要就是让我们求解矩形面积并,求解矩形面积并如果不用任何优化方式,那就是这么算的。
用矩形和减去矩形交集: $(20-10)(20-10)+(25-15)(25.5-15)-(20-15)*(20-15) = 180.0$ 这样算非常的耗费时间,因为每个矩形都需要两两配对,查看互相之间是否有交集。
优化方法
把这两个矩形分成三个
于是现在就变成了$(20-10)(15-10)+(25.5-10)(20-15)+(25.5-15)*(25-20)=180$
采取这个方法的好处
只需要从左往右扫,一步一更新即可
那这种方法需要有哪些信息呢?
- 每个新矩形的的高度。
- 每个新矩形的宽度。
那我们先从计算宽度说起
- 其实计算宽度特简单。我们把垂直于x的边单独挑出来。
- 然后按照x的大小排个序,隔位相减就可以得到。如$Kuan[0] = 15-10; Kuan[1] = 20-15;....$
那现在来计算矩形的高度
矩形高度怎么计算呢?这是整个扫描线最难理解的地方。
我先问一个问题:为什么二号矩形的高是$(10+(25.5-10))$呢?
* 很直观的回答就是:那是因为得算上1号矩形高,再加上2号矩形多出来的部分
那为什么三号矩形的高是$(25.5-5)$呢?
* 那是因为得用2号矩形的高那部分减去,减掉2号矩形下面多出来的部分。
那为什么有时候“多出来”是加上一个值,有时候“多出来”是减掉一个值呢?
这个问题其实也是得到高度最核心的问题,就是“入边”和“出边”的问题。
定义:在同一个矩形内,从左往右看,第一条看到的边为“入边”,第二条看到的边为“出边”
其实所谓的从左往右(也可以是从上往下),就是扫描线的方向。 当从左往右扫,遇到入边的线,则对入边区间扫到进行+1操作,遇到出边,那么对出边区间进行-1操作,这样子就可以解释“有时候“多出来”是加上一个值,有时候“多出来”是减掉一个值”这个问题了!
凭借刚才获得的知识,我们来思考步骤
- (出入边赋值已完成)
- 第一条为入边,区间为[10,20],则区间[10,20] +1(此时区间[10,20] = 1)
- 查看整个域的区间,只有[10,20]有值,则Kuan[0]*10 = 50
- 第二条边为入边,区间为[15,25.5],则[15,25.5]+1(此时区间[10,15]=1,[15,20]=2,[20,25.5]=1)
- 查看整个域区间,从[10,25.5]有值,则Kuan[1]*(25.5-10) = 77.5
- 第三条边为出边,区间从[10,20],则[10,20]-1(此时区间为[15,25.5] = 1)
- 查看整个区间,从[15,25.5]有值,则Kuan[2]*(25.5-15) = 52.5
- 第四条边为出边,区间从[15,25.5],此时-1,整个区间没掉
- 整个区间没值,遍历结束。
这下整个思路就非常清晰了
将上述的区间模型转化成线段树
问题1:
我这个下标可存不了25.5这种东西啊,而且它这个区间要是特别大,我的数组会存不下。
解决方案:
将区间离散化。
离散化(步骤)
- 把y坐标离散化
- 用一个区间数组记录每个区间的值:于是现在[10,15]成为块1,[15,20]成为块2,[20,25.5]成为块3。
- 则现在更新第一条入边[10,20]就变成更新[1,3],更新第二条边就变成更新[2,4],之后再查表全部乘起来即可
问题2:这个区间更新了之后,怎么维护区间信息,使得调用query()就可以返回总共存在值的区间长度?
这个和染色问题是一样的,用一个cover表示区间[left,right]被覆盖的次数,用len表示这个区间的合法长度,那query(1到n)的合法长度,自然就能返回 总共的区间长度了。
解题步骤:
建树:
int
根据线段树的模板,结合这题的样例,我们很快就建好了一棵树。
仔细观察,这棵树似乎和之前的线段树不一样,它的叶子节点的[l,r]不相等,而是差别为1,。 这是因为点对于求面积的题目毫无意义,我们最需要的是它每一个基础的“块”。
- 第一条为入边,区间为[1,3],则区间cover[1,3] +1(此时区间[1,3] = 1)
- query整个域的区间,得到len=10,则Kuan[0]*10 = 50
- 第二条边为入边,区间为[2,4],则cover[2,4]+1(此时如图所示)
- query整个区间,得到len = 15.5,则Kuan[1]*(25.5-10) = 77.5
(注意:这里只需要上推len,不需要下推cover至[2,3]和[3,4],也不需要上推cover至[1,4]。这就是线段树的强大之处:只要找到对应结点的区间能完全覆盖当前线段区间就可以回溯统计了,并不需要更新到叶子节点,这是线段树为什么效率高的原因)
6. 第三条边为出边,区间从[1,3],则[1,3]-1 7.query整个区间,得到10.5,则Kuan[2]*10.5 = 52.5
7. 第四条边为出边,区间从[15,25.5],此时-1,整个区间没掉 8. query整个区间,值为0,遍历结束。
AC代码
#include