双指针与扫描线

双指针

  • 不想写概述。

CF1788D Moving Dots

  • 题意略。

  • 赛时一直在考虑怎么对总集合点数反演,即设法做 dp 如 \(f_{i,j}\) 表示考虑完前 \(i\) 个有 \(j\) 个集合点的方案数。显然这东西做不了,它的后效性强制它再加两维(上一个选的是啥,上上个选的是啥,好像还需要上上个的方向),\(O(n^4)\),没机会。

  • 考虑对每个集合反演。容易看出如果两个点中间是空的(没有选任何点),且一者朝左,一者朝右,则两者贡献一个额外的集合点(初始有一个)。但是这样处理很不舒服,转而考虑直接对每个集合反演,容易看出集合中心一定有一个大小为 \(2\) 的基环,枚举这个基环,于是有一段前缀和一段后缀爱选不选。

  • 考虑枚举基环左端点,对每个基环左端点,枚举基环的右端点,并维护前后缀的指针,显然前缀指针单调向左,后缀指针单调向右,总复杂度 \(O(n^2)\),做完了。

扫描线

  • 扫描线是将二维中的一维转化为”时间维“的一种降维思想。

  • 具体来说,扫描线将二维的问题视作连续的 \(n\) 个一维问题,在扫到 \(x\) 处时处理 \(x\) 处的修改和询问,从而将二维平面降维成一维直线。我个人的代码习惯是从上向下扫(统一使用 OI 坐标系)。

  • 分别讨论几种情况怎么做:

    • 区间加,矩形查:维护原数组的前缀和数组,将询问转化为两个前缀和查询的差分。

    • 矩形加,区间查:维护原数组,将修改转化为一加一减(事实上,矩形加本身是在一阶前缀和上做的,所以是将修改差分了)。

    • 矩形加,矩形查:首先显然我们可以推广矩形加,区间查的办法,处理一个历史版本和即可解决。也可以仿照区间加区间查的树状数组,维护 \(\sum d\)\(\sum id\),显然它们在前缀和/差分阶上还是原数组这一阶的,故修改操作还是被转化为了一加一减,问题解决。

  • 另外,扫描线本身有严格的栈结构,故如果修改难以处理,可以考虑标记永久化然后暴力撤销。

P5490 【模板】扫描线

  • 题意:求 \(n\) 个矩形的并的面积。

  • 直接说经典做法吧:做扫描线,将矩形拆成 \(xl\) 处对 \([yl,yr]+1\)\(xr+1\) 处对 \([yl,yr]-1\)。然后每个时刻被覆盖的点数就是当前截到的面积。

  • 不过不管怎么做,我们首先得解决一个经典问题:区间 \(\pm 1\),询问区间 \(>0\) 的数的个数。

    • 这个问题如果不保证总非负,那么只有一个 \(O(n\sqrt{n})\) 做法,见分块(我目前只会多一个 \(\log\) 的)。这里不表,因为扫描线的过程中显然是非负的。

    • 在保证总非负的前提下,这个问题如果不保证对偶,那么可以维护区间最小值个数,单 \(\log\)。不过扫描线的加减操作是对偶的。

    • 对偶?那么有一个单 \(\log\) 做法:对线段树标记永久化。若某个区间被标记,则其内点数为区间长度,否则为儿子点数和。

  • 从而此问题 naive。至于能不能推广到二维的树套树做法,应当是至多理论可行,理由参看树套树-线套线。

UVA1608 不无聊的序列 Non-boring sequences

  • 题意:判断 \(S\) 是否 boring。所谓 boring,就是存在 \(S\) 的子串 \(s\) 满足 \(s\) 中不存在只出现一次的元素。

  • 数据范围:\(n\leqslant 2\times 10^5\)

  • 注意到 \(\forall l\in (pre_i,i],r\in [i,nxt_i)\),区间 \([l,r]\) 合法,这里 \(pre_i,nxt_i\) 表示和 \(i\) 相同的前驱/后继位置,若不存在则赋极小/极大。

  • 于是考虑构建二维平面,\(l\)\(x\) 轴,\(r\)\(y\) 轴,对每个 \(i\),我们将其转化为一个矩形覆盖。最后问题就是矩形面积并是否等于 \(\dfrac{n(n+1)}{2}\)

  • 显然树套树无意义也不划算,使用扫描线+差分实现,\(O(n\log n)\)

P3246 [HNOI2016] 序列

  • 题意:给定序列 \(a_{1\sim n}\)\(Q\) 组询问,形如求子串 \([l,r]\) 的所有子串的最小值之和。形式化地,求 \(\sum\limits_{l=L}^R \sum\limits_{r=l}^R \min_{i=l}^r a_i\)

  • 数据范围:\(n,Q\leqslant 10^5\)

  • 考虑 \(l,r\) 二维平面,发现只有满足 \(x\leqslant y\) 的点 \((x,y)\) 有意义,即一个左上三角。

  • 注意到 \(\forall L\leqslant l\leqslant r\leqslant R\)\((L,R)\) 在矩形 \((1,n),(l,r)\) 中,换言之,包含子串 \((l,r)\) 的所有串恰为二维平面上 \((l,r)\) 的左上矩形。

  • 考虑扫描线,不妨预处理出 \(pre_i\)\(\max_{j<i \land a_j<a_i} j\)\(nxt_i\)\(\min_{i<j \land a_i>a_j} j\) 的方式来将 \(chkmin\) 变成赋值。只要让先前的矩形覆盖记得把自己撤销掉,那么我们就退化成矩形加矩形求和。典了。

P5816 [CQOI2010] 内部白点

  • 题意略。审错题了好久!

  • 注意到似乎很难多轮变换,考察发现若一个点依赖一个生成点,则可以替换为依赖生成点的父本,故只会有一次,且无 \(-1\)

  • 故会变黑的就是所有严格十字的中心...考虑将十字抽象为一横一纵两条线的交点,从上向下做扫描线,把每列的线段拆成出现和消失维护之(故是单点修),每行求该行的极长横线和存在的竖线的交点个数,即区间查询。

  • 复杂度 \(O(n\log)\)

P4406 [CQOI2005] 三角形面积并

  • 题意略。

  • 求出所有关键点,包括原有点和交点。按 \(x\) 排序,从上向下扫,可以证明每两个 \(x\) 间的图形都是广义梯形(多个不交的梯形的并,梯形可能退化成三角形),于是套梯形体积计算公式就完事了。

  • 显然关键点是 \(O(n^2)\) 个,每次将当前的线和所有三角形暴力求相交线段 ,然后排序维护,具体来说是按左端点升序排序,然后如果交就延长当前这一段线段的右端点,否则开一个新线段,或者离散化后上线段树做区间覆盖,可以在 \(O(n\log n)\) 的时间求出答案,总复杂度 \(O(n^3\log)\)

  • 一点小问题是可能会有一边平行于 \(y\) 轴的三角形,计算时它们并不在两条线之间却作为底被扫上了。解决办法是同时跑两条线的求交,如果只有一个交到那就当这个三角形不存在。也可以随机旋转。

  • 太难写了...

P3722 [AH2017/HNOI2017] 影魔

  • 题意略。

  • 怎么看都很扫描线或者莫队啊,问题是贡献不独立,不仅不独立,还非常毒。

  • 考虑一下增量法,即加入 \(r\) 后,\(\forall i\in [l,r)\),点对 \((i,r)\) 的贡献是多少。

    • 注意到贡献的形式大体上关于 \(\max/\min(a_i,a_r)\)\(\max_{j=i+1}^{r-1} a_j\),不考虑第二项,先非常典地把 \(\max/\min(a_i,a_r)\) 拆一下。

    • 显然不容易都拆...不妨记录一个 \(pre_r\) 表示 \(r\) 前第一个比 \(r\) 大的数在哪里。于是变成两段...

    • 画图仔细分析一波,发现如果维护一个后缀最大值序列(不包括 \(r\)),从右向左扫,扫到 \(pre_r\) 之前(含 \(pre_r\),即 \(\geqslant pre_r\) 的位置),每个后缀最大值构成一个一类点对,非后缀最大值构成二类点对。扫到 \(pre_r\) 之后(即 \(<pre_r\) 的位置),每个后缀最大值构成二类点对,非后缀最大值不构成有效点对。

    • 之后将 \(r\) 加入维护这个后缀最大值序列,可以发现只要不撤销在势能分析下这一维护过程是 \(O(1)\) 的。在左边加一个 \(l-1\) 也不影响这个后缀最大值序列及其势能,也是均摊 \(O(1)\) 的。

    • 基于此,可以设计一个回滚莫队做法,\(O(n\sqrt{n})\),但常数颇大。

  • 考虑反演。即,关于 \(i\),考虑其对 \(l<i<r\) 的所有点对 \((l,r)\) 的贡献。

    • 不妨先将 \((l,r)\) 映射到平面上。我们知道这是一个上三角,于是所有 \(i\in (l,r)\)\((l,r)\) 对应当对应一个左上角为 \((1,n)\) 的矩阵。

    • 反复地把 \(a_i\) 矩阵修改上去(先不管怎么修改),最后我们可以得到所有 \((l,r)\) 对之间的最大值,并可由此 \(O(1)\) 推出其贡献,当然全记录下来就 TLE 了。

    • 考虑一下询问的形式。发现询问是一个贴着该上三角斜边的上三角内求和。我们可以把它等效成对应的矩形和。

    • 则问题变成扫描线+历史版本和(线套线这种依赖标记永久化且各种破坏线段树结构的东西显然不支持 \(chkmin\))。其实这是显然的——我是指,这是审完题就容易看出的。一个区间内的所有点对和一个区间的所有子区间,在形式上,差不多,何况点对的贡献依赖其对应的区间。故几乎一定是扫描线+历史版本和。

    • 但显然这个贡献非常没法算...

  • 看了题解。确实是考虑反演...但是...和我想的不太一样...关于 \(i\),记录 \(pre_i,nxt_i\),表示其前/后第一个大于它的位置。于是:

    • 不妨记 \(pre_i=l,nxt_i=r\)

    • 点对 \((l,r)\) 为一类点对。一定不会重复计算,因为这有点类似笛卡尔树,\(\in(l,r)\) 的其他点的 \(pre,nxt\) 会被 \(i\) 挡住。

    • 点对 \((l+1\sim i-1,r)\) 为二类点对,\((l,i+1\sim r-1)\) 也一样。

    • 点对 \((l+1\sim i-1,i+1\sim r-1)\) 不构成有效点对。

    • 两边内部的点对要由内部的情况来决定。注意到两种区间修分别需要按 \(l\) 和按 \(r\) 扫,那就扫两次完事。

    • 特别地,这处理不了长为 \(2\) 的区间,我们手动把它们加上就行了(可以当修改扔上去,也可以直接加到询问的答案里)。

  • 故扫描线维护之即可。这里有趣的一点是,修改矩阵退化为区间,于是可以放弃维护原数组,转而维护原数组的前缀和数组(可以认为是修改对应的撤销没有了),此时历史版本和即为前缀和的差分。\(O(n\log)\)

  • 此做法不可简单推广到非排列情况,但莫队那个可以。不过本做法的本质是笛卡尔树,而笛卡尔树在一定程度上可以说是这种取 \(\max\) 然后比较类问题的通解,故建出笛卡尔树后使劲操作肯定是可以的。

P5567 [SDOI2008] 立方体覆盖

  • 题意略。

  • 可以注意到,本质不同的坐标,每种只会有 \(2n\) 个。

  • 于是考虑暴力,我们开 \(n\) 棵线段树构成一个平面去扫它,即,枚举 \(x\),第 \(i\) 个线段树为 \(l:x,y=i\)\(n\) 这么小不要用那个魔怔树套树做法。

  • 考虑复杂度。每个立方体贡献 \(n\) 个修改操作,故总复杂度为 \(O(n^2\log)\)

P8868 [NOIP2022] 比赛

  • 题意:给定两个序列 \(A_{1\sim n},B_{1\sim n}\)\(Q\) 组询问,求 \(\sum\limits_{p=l}^r \sum\limits_{q=p}^r (\max_{i=p}^q A_i)(\max_{j=p}^q B_j)\)。对 \(2^{64}\) 取模。

  • 数据范围:\(n,Q\leqslant 10^5\)

  • 我其实觉得吧,这道题、序列、影魔这些题都差不多...基本就是几个方向:

    • 拉到二维平面上:扫描线(+历史版本和(因为“所有子区间”))。

    • 笛卡尔树:分治。也可以利用这个性质简化扫描线。

    • 利用上面两个性质操作一下:莫队(可能要二次离线)。

    • 只要舍得多付出复杂度,分块总能实现一些神奇的事情。

  • 我目前基本只会第一个方向,毕竟我的复杂线段树理论上学完了,实践能不能写出来...这不是正在写吗。笛卡尔树显然也是一个很值得深挖的方向,不难想到其和许多 dp 也有关联。莫队本质上只是优雅的暴力罢了,除了莫二离比较神奇之外好像都可以分块平替(当然分块更是个天坑)。

  • 好了,说回来。我们先上我会的 segbeats 解法,其他解法有空再说,老规矩把询问拉到二维平面上,我们希望点 \((p,q)\) 上的值是 \((\max_{i=p}^q A_i)(\max_{j=p}^q B_j)\)。呃呃,有点像序列加强版...

  • 首先单调栈搞一下,变成矩形赋值然后变成矩形加,不妨抛掉历史版本和和扫描线考虑这个线段树本身,我们希望支持的操作就是对 \(a,b\) 区间加(这里线段树里的 \(a,b\) 本身就是最大值,毕竟我们处理过了)和求 \(a\times b\),不妨记为 \(c\),的区间和。

  • 感觉挺典的...维护 \(a,b,c\),在叶节点处它们的定义就是刚才提到的,在非叶节点就是对应的区间和,于是 \(a\) 区间加对 \(c\) 的影响就是加上对应倍的 \(b\),OKOK。

  • 设法维护 \(c\) 的历史版本和,记其历史版本和数组为 \(h\),辅助数组 \(s=h-tc\),忽略 \(len\) 影响,设 \(a\) 区间加的值为 \(v\),于是 \(c'=c+vb,h'=h+c',s'=h'-tc'=h+c'-tc'=h+(1-t)c'\),注意到 \(h=(t-1)c+s\),于是 \(s'=(t-1)c+s+(1-t)c'=s+(1-t)(c'-c)=s+(1-t)vb\)

  • 显然这和序列那里是不同的,我们在 segbeats 那边谈过直接暴力对 \(s\) 打 tag 就很方便但这里显然我们不能简单打区间加 tag 了,我们得打“加多少倍的 \(b\)”的 tag,注意到其实 \(c\) 本身打的 tag 也是这种格式的。问题在于怎么合并这种 tag...

  • 显然 \(s\)\(c\) 的变动的形式是一样的,区别只在于 \(1-t\) 的系数。直接考虑怎么修 \(c\),注意到较晚的 tag 在向对面请求的时候需要计入对面已有的 tag 的影响,具体来说即 \(v'(a+v)=v'a+v'v\),忽略 \(len\) 影响后发现其实是对 \(c\)\(+a,+b,+1\) 三种系数 tag,简单操作之即可。复杂度 \(O(n\log)\)

posted @ 2023-01-15 10:04  未欣  阅读(47)  评论(0编辑  收藏  举报