2021-08-17 圣 诞 节 的 复 习

CDQ 分治

CDQ 分治是一种分治算法废话,大部分时候用于解决偏序问题,或者优化含有偏序形式的一些转移方程。

首先要知道,什么事偏序问题,即是一些物体含有若干属性 a i a_i ai b i b_i bi c i c_i ci
要求满足满足对于每个属性都有一个大小关系约束的数对 ( i , j ) (i,j) (i,j) 的数量,形如:

a i < a j a_i < a_j ai<aj 或者 a i > a j a_i > a_j ai>aj

b i < b j b_i < b_j bi<bj 或者 b i > b j b_i > b_j bi>bj

c i < c j c_i < c_j ci<cj 或者 c i > c j c_i >c_j ci>cj

要学习 CDQ 分治,就需要先复习一下什么是归并排序。

归并排序的主要步骤分为两步:归、并。

归:把一个大区间 [ l , r ] [l,r] [l,r] 分成 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r]。继续递归求解,使得每个子区间有序。

并:双指针扫两个子区间,可以扫一遍让 [ l , r ] [l,r] [l,r] 有序,如此以来可以 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) 解决问题吊打快排

二维偏序

一个很经典的二维偏序就是逆序对,我们重新审视一下逆序对的形式:

i d i < i d j id_i < id_j idi<idj i d i = i id_i=i idi=i

a i > a j a_i > a_j ai>aj

这里dark♂以用各种数据结构去搞,但是现在我们又知道了一种新的方法:CDQ 分治(说句闲话,虽然说理论上 CDQ 分治是分治算法,但很多时候它(她?)被当成数据结构来使用。

什么是 CDQ 分治?大体来说就是对于区间 [ l , r ] [l,r] [l,r] 分为 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 分别求解,然后计算 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 的贡献。

前面一句是不是和归并排序很像?所以 CDQ 分治大概写出来也和归并排序差别不大。

说回这个问题,考虑有一个区间 [ l , r ] [l,r] [l,r],它的两个子区间分别的逆序对已经计算出来了并且有序,此时右区间对于左区间已经天然满足了约束 1 1 1。考虑合并这两个区间时,怎么计算右区间对左区间的贡献。

我们如何使得大区间有序?使用两个指针 i , j i,j i,j 分别扫两个区间,如果 a i > a j a_i>a_j ai>aj 就先插入 j j j,否则插入 i i i。注意到了吗?我们的第二个约束 a i > a j a_i>a_j ai>aj 出现了!,如果 a i > a j a_i>a_j ai>aj,那么 a j < a k ( i ≤ k ≤ m i d ) a_j < a_k(i\le k \le mid) aj<ak(ikmid),所以 a j a_j aj 对左区间的贡献就是 m i d − i + 1 mid-i+1 midi+1,问题解决了!

如果 CDQ 只能解决这种普及组问题(云南省选:你礼貌吗?),那实在是太拉了,下面的几个例子也许可以展示下 CDQ 的厉害之处

三维偏序

也许应该用一个更文艺的名字:陌上花开。

给定 n n n 个数,每个数有 a i , b i , c i a_i,b_i,c_i ai,bi,ci 三个属性,求解有多少个数对 ( i , j ) (i,j) (i,j) 满足:

a i ≤ a j a_i \le a_j aiaj

b i ≤ b j b_i \le b_j bibj

c i ≤ c j c_i \le c_j cicj

怎么搞呢?

考虑一维一维的降维打击,如何降掉一个维度?最简单的办法是使其有序,我们先按 a i a_i ai 升序排列。然后套路的讨论 [ l , r ] [l,r] [l,r] 这个区间,假定我们计算了 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 并且使得这两个子区间 b i b_i bi 有序,我们此时知道了那些信息呢?

  • a i < a j ( i ∈ [ l , m i d ] , j ∈ [ m i d + 1 , r ] ) a_i < a_j(i \in [l,mid],j\in [mid+1,r]) ai<aj(i[l,mid],j[mid+1,r])
  • 子区间 b i b_i bi 有序。

考虑怎么算贡献。

首先两个区间的 a i a_i ai 显然满足要求。

由于 b i b_i bi 有序,所以考虑按之前说的方法插入,当 b i ≤ b j b_i \le b_j bibj 时满足了约束 2,终于我们只剩下一个约束,但似乎我们也没有统计这个约束的方法了?怎么可能!对右区间维护一个关于 c i c_i ci 的权值树状数组,每次出现 b i ≤ b j b_i \le b_j bibj,查询树状数组即可。

おすすめ

転載: blog.csdn.net/cryozwq/article/details/119763592