CDQ 分治学习笔记
引入
- 给定一个长度为 \(n\) 的序列,每一个元素都有一个键值 \(a\),记 \(f(i)\) 为 \(a_i \le a_j\) 且 \(i \ne j\) 的 \(j\) 的个数(一位偏序)。
- 求 \(d \in \left [ 0, n \right )\),\(f(i) = d\) 的 \(i\) 的数量。
- 这很简单,我们不做讨论。
- 那如果加一维 \(b\) 呢?(二维偏序)
- 只需要先按照 \(a\) 排序,这样就可以去掉 \(a\) 这一维的限制。
- 然后用你喜欢的方式维护逆序对即可。
- 再加一维呢?(三位偏序)
- 好像就没那么简单了……
正题
概念
cdq 分治主要用来解决二元组问题。
传入 \((l, r)\),则所求二元组(有序) \((i, j)\) 满足 \(l \le i, j \le r\)。
取该区间中点 \(m\)
- 若 \(l \le i, j \le m\),分治 \((l, m)\)。
- 若 \(m + 1 \le i, j \le r\),分治 \((m + 1, r)\)。
- 解决 \(l \le i \le m \lt j \le r\) 的情况。
模板
void cdq(int l, int r) {
int mid = (l + r) / 2;
cdq(l, mid), cdq(mid + 1, r);
// solve
}
例题
三维偏序
回到刚刚的问题,我们试着用 cdq 分治解决刚刚的问题。
考虑先按照 \(a\) 排序,就可以省下一维的复杂度。
此时就有 \(l \le i \le mid\)、\(mid + 1 \le j \le r\) 的情况。
先将 \(l \sim mid\) 和 \(mid + 1 \sim r\) 分别按照 \(b\) 排序。
虽然分别排序会打乱 \(a\) 的顺序,但是 \(\forall i \in \left [ l, mid \right ], j \in \left [ mid + 1, r \right ]\) 均有 \(a_i \lt a_j\)。
所以只需要分别双指针一下即可统计。
struct fenwick {
#define lowbit(x) (x & -x)
int c[N];
void change(int x, int v) {
for (; x <= k; x += lowbit(x)) {
c[x] += v;
}
}
int ask(int x) {
int ans = 0;
for (; x >= 1; x -= lowbit(x)) {
ans += c[x];
}
return ans;
}
} bit;
void cdq(int l, int r) {
if (l >= r)
return;
int mid = (l + r) / 2;
cdq(l, mid), cdq(mid + 1, r);
sort(a + l, a + mid + 1, [&](node x, node y) {
return (x.b != y.b) ? (x.b < y.b) : (x.c < y.c);
});
sort(a + mid + 1, a + r + 1, [&](node x, node y) {
return (x.b != y.b) ? (x.b < y.b) : (x.c < y.c);
});
int i = l, j = mid + 1;
for (; j <= r; j++) {
while (i <= mid && a[i].b <= a[j].b) {
bit.change(a[i].c, a[i].num);
i++;
}
a[j].ans += bit.ask(a[j].c);
}
for (int k = l; k < i; k++) {
bit.change(a[k].c, -a[k].num);
}
}
不过这也不一定只能用一个 cdq 分治,我们其实可以再在里面嵌套一个 cdq 分治,此时也可以维护三维偏序。
这种方法更加普遍,如四维偏序、五维偏序等也可以用这种方法。
代码我就不写了。

浙公网安备 33010602011771号