扫描线口胡

前言

本片博客写于 lxl 学长回校讲课后不久,是笔者的一篇学习笔记,在此会详细介绍扫描线的有关内容,知识点和题大部分是 lxl 的课件上的。如有勘误,欢迎指出!

前置

对信息的理解

对于维护的信息我们可以把与信息相关的每一种限制都看成平面上的一个维度,维度越高也就说明信息限制越多,对应的就越不好维护。通常作为维度的有时间、序列下标、数值等。

在一个 \(B\) 维直角坐标系下,第 \(i\) 维坐标在一个整数范围 \([l_i,r_i]\) 区间,内部所有信息就是 \(B\) 维正交范围。一维的正交范围称为区间,二维称为矩形,三维称长方体。

我们查询信息,实际就是选择一个 \(B\) 维正交范围,对于每一个维度查询的限制可能不相同。如果对一维没有限制,则这一维是平凡的可以降维;如果所有维度都有两条边的限制,则称这是一个 \(\text {2B-side}\) 的 \(B\) 维正交范围。

为延续 lxl 优秀的画图习惯,这里补一张图,下面一次是一到四 \(\text{side}\) 的矩形。

自由度体系

这个体系一般用于描述静态问题,我们根据询问的参数数量定义问题的自由度数。注意这里自由度的度数与维数不一定对应。一般将动态问题离线转化为静态问题会增加一自由度,也就是时间维。

扫描线和差分

实质

扫描线是将高自由度问题转化为动态低自由度问题的方式,扫描线扫不同的维,扫不同的方向,会将原静态问题转换为不同形式的动态问题。

一维扫描线

对于一个静态的二维问题,我们可以用扫描线扫一维,数据结构维护另一维。在扫的过程中我们会在数据结构上进行一些修改查询,如果查询可以差分就直接差分,因为差分在不对答案造成任何影响的情况下能够降低问题难度,所以能差分就无条件差分即可。

我们还可以换一个角度看扫描线,我们可以从序列的角度看待问题。我们的扫描线扫到的位置实际就是修改查询区间的右端点,我们维护一个数据结构,它支持对于当前的右端点 \(r\),维护所有左端点 \(l\) 的信息。

关于扫描线扫一个多 \(\text{side}\) 的矩形,我们我们也许不好维护所有左端点的信息,但是能够很好维护前缀信息(也就是区间 \([1,r]\) 的信息),这时我们会用差分。

差分方法

这里列举一点经典的差分方法:

  1. 对于序列上的区间 \([l,r]\):差分为 \([1,r]-[1,l-1]\) 的前缀。
  2. 对于树上的路径 \((u,v)\):差分为 \((rt,u)+(rt,v)-(rt,lca)-(rt,fa_{lca})\)。
  3. 对于树上子树修改查询操作:用 dfs 序转化成区间问题。
  4. 对于树上子树修改、单点查询:可以转化成单点修改、前缀查询。

类似的转化还有很多,这些都需要我们有耐心步步转化,拆解问题。

差分的原因

根据 lxl 所讲,差分的问题就是"自由度"的问题。通过差分我们往往能将一个高自由度问题以极小代价转换为低维问题,而维数越低,信息越好处理。

也许有读者会问差分的常数,这一点不用担心。因为计算机做加减运算是比较快的,它真正慢的地方在于查询区间,你差分成前缀最多不到 \(\log\) 个区间,而不差分可能出现接近 \(2\log\) 个,所以差分的代价是很小的,你也可以自己手搓一点小数据试一试。

对于 \(\text{4-side}\) 矩形的扫描线

在可差分的前提下,我们可先将其差分成两个 \(\text{3-side}\) 的矩形。对于只有一条边的维度,我们可以用扫描线直接干掉,于是就把原问题变成一维动态问题了,这时候选择合适的数据结构维护即可。

基础题

A

题面

给一个长为 \(n\) 的序列,有 \(m\) 次查询,每次查区间 \([l,r]\) 中值在 \([x,y]\) 内的元素个数。

题解

这个问题叫二位数点,是一个非常经典但基础的扫描线,我们把序列和值域当成信息的两维,对于每次查询我们都相当于在平面内选择一个矩形,数矩形内点数。然后就按照上面讲的 \(\text{4-side}\) 矩形做法做,扫描线加上树状数组即可。

B

题面

给一个二维平面,上面有 \(n\) 个矩形,每个矩形坐标范围在 \([1,n]\) 以内;有 \(m\) 次查询,每次查询给定一个二维平面上的点,求这个点被多少矩形包含。

题解

就是普通的扫面积、单点查。

过渡

接下来的内容与扫描线的运用有关,分为三种技巧,每种技巧笔者整理了一点经典的题。但不能保证题解写得很详细。

反演

这是一种正难则反的思想,对于正常查询操作不好做的题,我们可以考虑每个位置对答案的贡献。具体可以看后面的题。

数据结构

给一个长为 \(n\) 的序列,\(m\) 次查询:如果将区间 \([l,r]\) 中所有数都 \(+1\),那么整个序列有多少个不同的数?询问间独立。

要求:单 \(\log\)。

题解

我们首先可以考虑不同值对答案的贡献,因为不同的值贡献独立。但我们发现如果直接查区间外的和区间内的就会很麻烦,总之不好处理,于是就可以进行反演。

我们考虑每个位置对哪些询问有贡献,常见的处理方法是容斥一下,考虑每个位置对哪些询问没有贡献,这是相对好做的。于是我们考虑怎么样一个询问是满足条件的。

所以对于每个值 \(x\) 考虑什么样的查询区间会导致其无贡献。显然是原来是 \(x\) 的数全部被包含,且原来是 \(x-1\) 的数全部没有被包含。这些限制实际上在通过预处理每个值的位置后变成区间左右端点。

所以现在考虑将区间的左右端点拿去建立平面直角坐标系,然后将满足条件的矩形加进去。因为矩形个数是 \(O(\text{x-1出现次数})\) 级别的,所以一共有线性个矩形,然后就是扫描线随便搞搞结束,时间复杂度 \(O((n+m)\log n)\)。

窗口的星星

平面上有一些点,点有点权。给你固定长宽的矩形,求能覆盖的最大权值和。

数据范围 :\(1\le n\le10^4,0\le x,y\le2^{31}\)。

题解

考虑如果扫描线去扫一维,那么第二维似乎无法处理,因为我们需要找到一段最值区间,这个不好维护。于是考虑每个点对矩形的一个顶点在那些位置有贡献,最后我们其实只需要找到全局最值点即可。

the soldier of love

给 \(n\) 个区间,有 \(m\) 次询问,每次询问给出一些点,求对于这些点而言,有多少个初始给定的区间包含了至少这些点中的一个点。

要求:单 \(\log\)。

题解

因为区间可能包含若干点,不好统计,所以考虑容斥。我们还是用反演的思路,我们去找相邻两点之间的矩形个数。所以就把初始矩形抽象成一个点,查询就是查线性个区间。

区间子区间

这种题一般是查询一个区间内所有子区间的信息,一般的方法是把一个单独的区间 看成平面上的点,查询的区间就是一个上三角。对于这种图形有不同的处理方式,因为平面右下半边贡献为零,所以可以把上三角补成矩形,且通常是 \(\text{2-side}\) 矩形。

可以扫描线扫一维。于是我们需要做的就是区间维护、查询区间历史和。那么现在考虑如何用线段树维护历史和?

历史和线段树

我们需要维护两个序列 \(A\) 和 \(B\),其中 \(A\) 代表当前序列,\(B\) 代表每个位置维护的历史和。考虑每次操作是修改 \(A\),每次查询是查询 \(B\),所以我们需要用一些东西将 \(A\) 和 \(B\) 联系起来。考虑我们在每个时刻都会将 \(A\) 中的信息累加到 \(B\) 中,所以现在需要对 \(B\) 维护一种特殊的标记 ,这种标记是全局累加每个位置对应的 \(A\) 的信息。

有了特殊的标记后我们就要思考如何合并并且下放标记?比如现在依次有修改操作 \(+a\)、历史合并 \(b\) 次、修改操作 \(+c\) 和历史合并 \(d\) 次这四种标记,我们应该以何种方式合并呢?首先明确一点,对于历史合并的标记,可以当成先修改再合并,也可以将合并往后挪一个单位时间变成先合并再修改,这是不影响的。这里讲后者。

现在当成有历史合并 \(a\) 次、修改操作 \(+b\)、历史合并 \(c\) 次、修改操作 \(+d\)。如果我们把合并的操作放一起 会怎么样?现在就变成了历史合并 \(a+c\) 次、修改操作 \(+b+d\)。但是这肯定还不对,因为**"修改操作 \(+b\)"对"历史合并 \(c\) 次"造成了影响** !考虑到每次合并都会少一个额外的值 \(W\),于是我们就对于一系列操作打包维护,也就是维护一个三元组 \((a, b, W)\)。合并三元组的时候修改操作就直接累加;对于 \(B\) 序列需要加上之前的损失值 ;对于 \(W\) 不仅需要加上原来的额外值 ,还有这一次的损失值 \(W'=b*c\) 。至此标记的合并下放也就结束了,其他地方就照搬普通线段树即可。其实对于历史和线段树还有一些更优秀的理解,也就是把每个节点当成矩阵,但在此不赘述,只放一个学长写的博客。

一点题

放一个板子-->this

当然这个题可以用 st 表薄纱区间和线段树。

CF526F

给定一个 \(n\times n\) 的棋盘,其中有 \(n\) 个棋子,每行每列恰好有一个棋子。对于所有的 \(1≤k≤n\),求有多少个 \(k\times k\) 的子棋盘中恰好有 \(k\) 个棋子,输出其总和。

题解

换维扫描线

一点杂题

即便看不到未来

查询静态区间中长度为一到十的极长值域连续段个数。

题解

可以考虑扫描线。若我们扫区间右端点,那么我们需要维护每一个左端点的答案。首先思考现在新加入一个数 \(x\) 后会有什么影响?

假设 \(x\) 上一次出现的位置是 \(las_x\),那么对于左端点在 \([1,las_x]\) 的区间都没有影响。又考虑到我们只需要维护十以内的极长段,所以就对不同的长度都维护一个数据结构。并且加入一个数只会影响相邻的值域 ,所以我们可以把 \([x-11,x+11]\) 中最后出现的位置拿出来暴力求解。

首先解释为什么需要拿出值域在 \([x-11,x+11]\) 中的数。对于值域在 \([x-9,x+9]\) 中的数,它们会和 \(x\) 产生贡献,所以肯定要;但是考虑如果 \(x\) 成为一段的开头或结尾 时,这一段可能会长度大于十,这时我们需要判断一下这一段的长度,所以要往后看两位。如果看一位你只能知道他是否大于等于十而不是十一。

然后就是考虑从右往左 更新答案。如果枚举到的值 \(y\) 在包含 \(x\) 连续段的开头或结尾,从当前位置开始到后面某一段贡献都会加一;如果枚举的 \(y\) 将两小段连在一起,就停止两小段的区间加,开始新的连续段的区间修改。最后单点查。所以可以差分一下用树状数组维护。时间复杂度 \(O((n+m)k\log n)\)。

代码很短就不放了。

相关推荐
Nekopedia4 个月前
线段树知识乱讲
ds
lzhlizihang5 个月前
使用DolphinScheduler调度实现sqoop增量导入时遇到 Caused by:Class QueryResult not found 错误解决
hadoop·报错·sqoop·ds
EricWang13585 个月前
IDS Clearing House Core 项目入门
ds
Qres8216 个月前
[SCOI2014] 方伯伯的玉米田(dp+树状数组维护行列)
数据结构·dp·ds
苡~1 年前
dolphinscheduler海豚调度(四)钉钉告警
钉钉·ds·钉钉告警·海豚调度·钉钉机器人
春人.1 年前
安卓移动设备使用DS file文件管理工具远程访问本地群晖NAS文件
安卓·ds
别摸我的键盘2 年前
dolphinschedule配置企微告警服务(WeChat群组)
企业微信·监控·ds·海豚调度器
飞鸟的心情2 年前
普通CRUD后台管理项目的核心包装升级
java·mybatis·ds