算法学习笔记(44): 二维问题小计

首先需要理解什么是二维问题。

$n$ 维空间体系:将元素变成 $n$ 维空间中的点,将范围变成 $n$ 维空间中的正交范围。

二维问题就是每一个元素都可以看作一个平面上的坐标 \((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


所以讲来讲去,二维问题到底是怎样的一个问题,有没有什么通法来解决呢?个人理解,仅供参考

二维问题,大部分情况下是表面上的一维问题。但是一维能够维护的信息太少太少了,所以需要更多的信息,例如时间,数值来增加可以维护的信息量,从而更好的解决问题。

而通法……我太菜了,没有通法

posted @ 2024-01-31 20:55  jeefy  阅读(94)  评论(1编辑  收藏  举报