Loading

二维数点技巧

这是一种比较常见的数据结构技巧,所以单独开一个坑。

概论

看上去很高端,实际上就是解决矩形求和的问题,有的时候还支持修改。

使用这种技巧有一个重要的前提:离线!

二维数点

首先这个技巧可以很好地解决二维数点问题。我们可以先来总结一下有哪些问题可以转化成二维数点问题,总的来说,如果一个序列上的问题可以转化成和它的上一个(可能是上一个颜色相同的位置等等)的差值(或者其它计算方式),我们可以把 \((i,pre_i)\) 看成一个点,这样区间查询相当于在某一个矩形中查询。注意这样的话,最终其实形成了一个右下三角,即直线 \(y=x\) 之上是没有值的,这样在矩形不好维护的时候,可以利用这个性质,扩展矩形从而使得它与某一条边界贴起来(为什么要这样等会说)。

还有一种形式是把行当成横坐标,列当成纵坐标,这样一个区间查询问题就变成了单点查询。当然修改就变成了矩形操作。

这样,我们只需要考虑二维数点问题。

静态

先从没有修改的开始。

这就能很好地转化成矩形加,全局求最大值。考虑把矩形加操作按行差分一下。然后维护一行的线段树,从上往下扫,遇到一行,就把当前行的影响算入,相当于边求前缀和边算答案。这东西和扫描线很像,我们就叫它 扫描线-plus 技巧吧。

P3582 Solution

当然这也可以解决静态数颜色问题,你只要把一个位置看成 \((i,pre_i)\),其中 \(pre_i\) 表示与位置 \(i\) 颜色相同的上一个位置。这样的话,如果查询 \([l,r]\),那么相当于我们要数有多少个点的 \(pre\)\(l\) 前,即统计 \(x\in [l,r],y\in [0,l)\) 内点的个数。直接用上述套路用树状数组扫一遍就行了。

动态

这技巧的精神所在即是可以很好地维护动态问题。

比如区间数颜色的问题,你发现静态的时候,用莫队会更好写,而且常数小,跑起来差不多。

但是如果带上修改呢?

先看比较简单的版本:

这题可以把问题转化成,每一个位置和这个位置上颜色上一次出现位置的差之和。这样问题就变成了和 \(pre_i\) 有关的二维数点了。然后静态做的话是比较好做的,就直接算出每个点的 \(pre\),然后在矩阵的对应位置加上其距离就行了。

现在来考虑动态的修改。每次修改一个位置,最多会影响到 \(3\) 个位置的 \(pre\)。我们把每次修改拆成 \(3\) 个。然后采用 cdq 分治,每次考虑左边的修改对右边询问的影响。然后做完当前询问之后直接撤销掉左边的修改,然后继续向上递归做就行了。

record

所以我们基本是采用 cdq 分治,来把动态问题静态化

除了这种,更经典的还有区间数颜色的修改版本,也就是:

利用 ODT 可以算出每次区间覆盖的时候,哪些点的 \(pre\) 会改掉。并且类似于 ODT 的复杂度均摊分析,可以发现 \(pre\) 修改的个数不会超过 \(O(n+m)\) 级别。然后后面套一个上面的 cdq 加 扫描线-plus 来做就行了。

当然在 ODT 求修改 \(pre\) 点的时候有致死量的细节,可以考虑对每个颜色再维护一个 set 来存所有区间。每次修改先把可能会变的存下来,最后扫一遍再存入询问会更好做一点。

record

较为容易的,还有这题:

还是一样采用 ODT 压缩同色段的思想,和上面差不多,只不过这题可能是把行列号当成横纵坐标。

考虑插入一段颜色对答案的贡献。我们假设当前这段颜色是 \([l,r]\),然后同色的上一整段是 \([l_1,r_1]\),后一整段是 \([l_2,r_2]\),这个可以像 ODT 一样用 set 维护。那么当前的贡献是在 \(L\in[r_1+1,r],R\in[l,l_2-1]\) 矩形上加上当前值。

这样就变成了矩形加单点求值。鉴于是动态问题,直接套一个 cdq 就做完了。

\(\color{red}{\texttt{Unsolved}}\)

posted @ 2022-08-10 21:34  ZCETHAN  阅读(947)  评论(0编辑  收藏  举报