算法学习笔记(44): 二维问题小计
首先需要理解什么是二维问题。
二维问题就是每一个元素都可以看作一个平面上的坐标 \((x, y)\)。其中一维可以是下标,时间,值,dfn
,甚至是一个函数 \((x, f(x))\)。
经典的二维问题实际上就是矩形加,矩形取 \(\max\),单点查,线查,矩形查。
而二维问题一般是通过某种建模方式变成上述操作进行操作。
- \(\color{blue} \bf \Delta\) 区间一次问题
例如给定序列,多次询问一个区间是否有重复的数。
虽然这原本是一个一维问题,但是可以升维更简单的处理。
- 解法 \(1\):经典方法,令 \(pre_i\) 表示 \(i\) 前面第一个与它相同的位置,那么一个区间没有重复的数当且仅当 \(\max_{i = l}^r pre_i \lt l\),于是升维之后变成了一个简单的区间 \(\max\) 问题。
- 解法 \(2\):将每一种数单独处理,设 \(f_x(i)\) 表示值为 \(x\) 的第 \(i\) 个数的位置,那么一个区间 \([l, r]\) 如果只被 \(x\) 覆盖了一次,那么需要满足 \(l \in (f_x(i), f_{x}(i + 1)]\),\(r \in [f_x(i + 1), f_x(i + 2))\)。将询问看作二维平面上的一个点,那么被贡献到的矩形有 \(O(n)\) 个,而一个区间全部都只出现了一次,那么它一定被贡献的区间长度次,于是问题转化为矩形加,单点查的问题,只是需要离线,并且常数略大。
- 解法 \(3\):类同解法 \(2\),只是考虑被 \(x\) 覆盖了不止一次的区间,那么需要满足 \(l \le f_x(i)\),\(r \ge f_x(i + 1)\),这样的矩形也是 \(O(n)\) 个的。
- ……或许还有更多的想法
对于解法 \(1\) 可以看作一个很好的套路,被玩烂了,但是解法 \(2/3\) 就非常有意思。
我们首先将每一个点对于区间的贡献拆了出来,也就是拆贡献。将自由度为二的问题看作了平面上的点,拆除的贡献是一个矩形贡献,那么就可以变成经典的二维问题了。
例:BZOJ#3489. A simple rmq problem 在 \([l,r]\) 之间找到最大的在这个区间里只出现过一次的数。
这用 解法 \(1\) 其实也可做,但是如果需要判断一个数是否只是出现了一次,那么不仅需要前驱 \(pre_i\),还需要后继 \(post_i\),当 \(i \in [l, r]\),\(pre_i \lt l\) 并且 \(post_i \gt r\) 的时候才可以对答案做出贡献,这是一个三维偏序问题,利用 cdq
分治可以做到 \(O(n \log^2 n)\) 的复杂度。
利用 解法 \(2\) 来做无论是代码还是思路都将十分的简单,因为我们只需要将提及的矩形加贡献变为矩形取 \(\max\) 贡献即可,利用扫描线与 set
可以做到 \(O(n \log^2 n + q \log n)\) 的复杂度,注意到这个是可以转化为可持久化线段树支持在线的。
- \(\color{blue} \bf \Delta\) 换维扫描线
将动态的一维问题转化为静态的二维问题,实际上就是增加了时间这一维度。
这里的换维扫描线实际上就是将对于时间维的扫描线(动态的一维问题)转化为对于序列的扫描线(另一个动态的一维问题)
例如维护一个序列,支持:
- 区间 \(+\)
- 单点求历史最值
这道题实际上可以利用历史版本最值线段树完成……但是这里不是讲这个的。
历史版本最值线段树可以参考:# 算法学习笔记(34): 矩阵乘法与线段树标记。
如果我不会这个神秘的科技怎么办?如果以序列为横坐标,时间为纵坐标:
那么区间修改形如红线,而查询最值形如蓝线
上下略微有些颠倒,不过问题不大。
如果我们竖着来扫描线,那么修改就变成了单点修改,而蓝线也就是前缀查询。
而历史最值呢?实际上也就是一个前缀最值问题,这是容易维护的。
- 例题:UR #19 前进四 维护一个序列 \(A\),单点修改,查询某个后缀后缀最小值的个数。
首先对于每次询问暴力做是简单的,注意到我们只需要维护两个变量:\(mn, cnt\) 表示后缀最小值和变化的次数。
每次 \(mn \leftarrow \min(mn, A_i)\),如果 \(mn\) 改变了,那么 \(cnt \leftarrow cnt + 1\)。
我们对于其多加一个下标 \(t\),那么 \(mn_t \leftarrow \min(mn_t, A_{t, i})\)。
注意到 \(A_{t, i}\) 在 \(i\) 固定的时候有若干连续段的值是相等的,这样的段是 \(O(q)\) 的,考虑同统一进行上述转移,其实也就是换维扫描线,那么问题转化为维护一个序列:
- 区间取 \(\min\)
- 单点查询一个点被取 \(\min\) 变小的次数
这是 seg beats
需要完成的事,不在本文讨论范畴内。
还有一些利用这个方法完成的题:
我们说了序列上的二维问题,对于树上的二维问题又是什么呢?
- \(\color{blue} \bf \Delta\) 树上二维问题
一般来说,我们需要利用 dfn
序的映射,因为它具有很好的性质。
- 例:定义一个点对 \((i, j)\) 是好的当且仅当:\(\exists k \in N^{*}, \left \lfloor \frac{i}{k} \right \rfloor = j\)。给定一棵 \(n\) 个点的树,树上点的权值互不相同,每次询问给定一个路径 \((x,y)\),问路径上多少个点对 \((u, v)\) 满足 \(u \ne v\) 且 \((a_u, a_v)\) 是好的。\(1 \le n, q, V \le 5 \times 10^4\)。
对于每一个 \(x\) 可以 \(O(\sqrt m)\) 预处理出可以与那些点形成贡献,考虑这些点对会对那些路径造成贡献。
- 点对非祖孙关系,那么如果路径两个端点分别在点对的子树内,那么可以被贡献。
- 点对为祖孙关系,若 \(x\) 是浅的那个,那么一个端点 \(\le dfn_x\) 或者 \(\ge dfn_x + siz_x\),另一个端点 \(\ge dfn_y\) 且 \(\lt dfn_y + siz_y\)。
发现将左右端点看作二维平面上的点,那么一个点对会导致 \(O(1)\) 个矩形加法,于是扫描线即可,可持久化之后可以强制在线。
- 例:用 \(n\) 个节点建出了了两棵有根树,询问两棵树某两个子树内的节点是否有交。
同一个节点在两个树上的 dfn
是不同的,所以自然是一个二维的元素。
由于子树内的 dfn
是一段连续的区间,那么问题转化为判定一个矩形内是否存在一个点,那么也就是经典的二维数点问题,随便做啦。
这个问题实际上是 [IOI2018] werewolf 狼人 的后半部分,前一半部分是 kruskal 重构树,可见:# 算法学习笔记(30):Kruskal 重构树
一道强化的问题,带修:Intersection of Permutations。
所以讲来讲去,二维问题到底是怎样的一个问题,有没有什么通法来解决呢?个人理解,仅供参考
二维问题,大部分情况下是表面上的一维问题。但是一维能够维护的信息太少太少了,所以需要更多的信息,例如时间,数值来增加可以维护的信息量,从而更好的解决问题。
而通法……我太菜了,没有通法