【扫描线】从头到尾弄懂扫描线 是否push_down典例分析
"智障都能看懂的扫描线”
扫描线算法(有基础者可以直接跳过)
扫描线算法,顾名思义即是在平面中,用一条水平/竖直于坐标轴的直线去“扫描”图形,其优点在于配合离散化,只需要考虑那些”有意义“的点,通常是图形轮廓的端点,在维护区间可并的信息时可以使用线段树维护以优化,平时说到扫描线多指线段树优化的扫描线。
下面是扫描线的一个例子,我们可以把一个二维的图形转化到1维进行处理。
假设我们现在正在处理平面上的一些矩形(覆盖型扫描线)。
我们维护这条直线,直线上的每一条线段维护这段目前被覆盖的次数,让这条直线从下向上平移。
(本文默认使用横扫描线)
每当遇见一个矩形的下边框时,就将这段,区间被覆盖的次数加一,代表矩形覆盖了这段线段,遇到上端点就将该区间的覆盖次数减一。
每条线段维护的数表示被覆盖的次数。
在到达矩形上边框时减去一的覆盖次数。
这样就可以处理出许多信息,比如面积并,就可以使用目前的覆盖长度坐标的变化值以求出面积。
时间复杂度为。
扫描线算法的优化
现在再考虑使用数据结构优化扫描线。
发现每个矩形可以看作一个在时刻的区间修改和一个在时刻的区间修改 , (默认每个点x维护的线段是)
那么我们可以尝试用线段树来处理区间修改。
区间覆盖型扫描线 (太简单了可直接跳过)
例: 求矩形的面积并
每个节点需要维护两个值,一个表示覆盖次数(?),一个表示该区间覆盖的总长度。
首先我们发现很明显不能让每个点维护这条线段被覆盖的次数,因为当这条线段的覆盖次数减到0了,为了维护信息,就需要向下去查询……就要使两个儿子的覆盖次数减一,大线段会分别去更新小线段,知道叶子节点或覆盖次数仍大于0的节点,复杂度会上升而且不好维护。
考虑每个节点维护恰好被一次修改完整覆盖的次数
继续思考会发现维护的东西的一些性质:
1.区间加一和区间减一是一一对应的
2.区间减一定出现在对应的区间加后
对于每个区间加,减,访问到的线段树上的节点都是完全相同的,并且这些点的值至少为1,所以直接在这些点上修改即可。如下
假设我们有两个矩形,如图(为了方便阐述,我们默认没有离散化,每个点维护线段)
首先先对于两个矩形的下边框都修改后,线段树应该是长成下面这个样子的
然后我们现在要对节点进行减1,按照普通线段树的思想,我们需要在修改被分配到之后将的Lazy Tag 下放,如图。
这本质是浪费了一大堆操作而且无法维护该区间未被覆盖的值,的覆盖次数仍然是1,但是由于扫描线的特征,在你试图让的值减1时,的值一定大于1,所以我们可以自信地无视这个“Lazy Tag” ,直接向下更改即可。
这时我们就会发现干脆直接不要Lazy Tag了,直接维护该节点Val值即可。
具体实现
维护恰好被完整覆盖的次数和区间内被覆盖的长度,修改时当目前节点被修改区间包含直接修改该值即可,若未被包含,下放更新。
上传值:
若这个节点的覆盖次数不为0,则该区间长度
若为0,则为
每次被覆盖的总长直接查询即可
这种类型的题目通常具有一种特性:无区别性,被覆盖一次,两次,n次都无区别,所以要维护需要的信息和覆盖次数,大区间的覆盖次数和小区间无关!。
区间求和/最值型扫描线 (太简单了可直接跳过)
例:平面上有一些点,有点权,求用大小固定的矩形可以框到的最点权和。
两边边框无法框到等价于一边边框无法框到,左闭右开-1处理.
不讨论题目细节,主要讨论扫描线思想。
先将每个点扩展成为一个矩形, 然后变为选一个点使得这个点被覆盖的权值和最大。
仍然是扫描线,但是我们每次的修改改为和的值w为覆盖的权值,与上面不同的是,这里小区间会影响到大区间了,小区间里加了权值,大区间的max会改变,而且我们维护的值与覆盖是呈直接关联的。
这里的线段树就需要懒标记和了,因为大区间的Lazy Tag被下放之后可以让小区间的值变大,以更改大区间最值,
目前权值如下,
我们需要更新
与前面的覆盖型扫描线对比,小区间被覆盖的次数不(直接)影响大区间的被覆盖的总长度,大区间的覆盖次数为0后自然会从小区间上传值。
和普通的“区间加,区间max查询”的线段树相同维护,push up/down即可。
什么时候的分析
对于前面一类问题,我们会发现,我们考虑的“线段”被覆盖一次,两次,100次都是等价的,也就是说当一个区间已经被覆盖了,下面的小区间是不会对它产生贡献的,如下:
这类问题的特点:
-
我们维护的是区间被覆盖的总长度时,子区间增加覆盖次数对大区间没有影响
-
我们的维护、查询的值和覆盖次数不成直接相关性,通常是“覆盖的长度、覆盖的最值……”
-
子区间内的所有段修改后并不是对于父区间有直接贡献,比如上图中的对父区间维护的“覆盖次数”就没有影响
下传lazy Tag会使后面的查询需要访问这个节点的子节点,如下,
加入现在修改[5.5],那么会将[1,5]的懒标记下放
变为如下情况
而下一次将的覆盖次数减为0时,就需要向下去“找”这些lazy Tag,因为要维护这些信息,需要知道子区间被覆盖的长度,复杂度就会大幅度退化,单次修改可以退化至
所以直接让线段树上的节点维护被恰好完整覆盖的次数,当区间被包含时直接在该点修改即可。
这类问题也可以通过维护特殊的和Lazy Tag实现,有兴趣的朋友可以自行学习,但由于不好理解,不建议使用。
不要拘泥于“遇见区间修改就push_down”,线段树不只是一种数据结构,也是一种思想,一种工具,应在线段树的基础上灵活运用各种思想
对于后一类问题,我们发现这个区间里面的每次小修改都会对父区间的最值产生直接影响,就像刚刚的这个例子,子区间求和后会对父区间产生影响。
这类问题就需要将懒标记下放,通常有以下特点:
-
覆盖情况和查寻值呈正相关
-
区间内每个点都对区间有贡献
-
查询一个点的子节点不许要不断的向下查询*
* :前面那类问题当这个子区间的覆盖次数为0时就需要知道继续子区间的覆盖长度等值,所以使用push_down会去更改大量节点的值,而这类问题只需要知道两个儿子的val即可。
类似普通线段树地维护即可
维护更复杂信息的扫描线
例:有一个 n 行 m 列的网格。其中有 k+1 个格子着火了。每个时刻,火会蔓延至相邻的格子(八联通)。现在给出其中 k 个着火的格子,请确定第 k+1 格子,使得网格被烧完的用时最短。
(我也不知到为啥是黑题)
先二分答案后即是要求极差,扫描线求“没有被覆盖坐标的最大、最小值”。
考虑维护每个子区间内没有被覆盖的最小、最大值和恰好被覆盖的次数(同“区间覆盖型扫描线”)
,表示该线段被整条覆盖的次数,同普通扫描线。
表示该线段中最左边没有被覆盖的点的y,若不存在,则为
表示给线段中最右边没有被覆盖的点的y,若不存在,则为
考虑怎么push_up
那么我们只用每次查询即可求出当前x的最大y和最小y了
考虑如何维护push_up
如果该线段被覆盖,,那么分别为,因为全线段被覆盖。
如果该线段未被直接完全覆盖,且不为叶子节点,则查询左儿子,右儿子的分别取即可。
若为叶子节点且未被覆盖,则最大,最小值均为该节点维护的区间(点)。
因为码量较大,篇幅有限,为了代码可读性不宜压行,故在此只放出解析思维完整代码见下。
例: 求矩形面积并的周长,包含内空心周长
将周长分为横纵计算,
维护区间覆盖长度之和,易发现这段造成的周长就是目前的覆盖长度之和减去操作之前对应的覆盖长度的绝对值,这样可以计算到所有横着的线段对答案的贡献。
如图,红色的线段是对答案有贡献的横线段,三个坐标对应的覆盖长度为
最上面那条红色线段会在被计算,
中间右边的那条红色线段会被计算
中间左边会被计算
下方的红色线段会被计算
那么求到的差的绝对值就是这段对答案的贡献,
和普通的覆盖式线段树一样计算即可。
再计算一边纵扫描线的贡献即可。
很长的代码
例:求空间中长方体的体积并
[HDU 3642] hdu没了呜呜呜呜呜
发现将扫描线中的线段树换成二维线段树即可类似地完成问题(扫描面),但是发现树套树无法完成不的操作,因为树套树本质是把矩形转化成了横线段和纵线段,所以考虑使用另一种二位线段树--“矩阵树”:维护一棵四叉树,分别维护四个子矩阵的信息。
每个点于普通扫描线相同,维护子矩阵的“完整覆盖次数”和“覆盖面积之和”即可,如下
不需要push_down
时间复杂度仍然为
整理仍在更新,略等(咕
upd : 1 .10 加入了部分代码
本文已经结束了。本文作者:ღꦿ࿐(DeepSea),转载请注明原文链接:https://www.cnblogs.com/Dreamerkk/p/17970999,谢谢你的阅读或转载!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步