Skip to content
本博客自 2023 年 4 月 4 日起转为归档状态,可能不再发表新的博文。点此了解博主的竞赛生涯
This blog has been archived by the owner since April 4, 2023. It may no longer have new updates.

CDQ 分治学习笔记

检测到 KaTeX 加载失败,可能会导致文中的数学公式无法正常渲染。

CDQ 分治是 OI 中的一个比较常用的分治算法。该算法最早由 IOI2008 金牌得主陈丹琦提出,并因此得名。

本文主要讲解使用 CDQ 分治解决点对三维偏序问题。

点对三维偏序问题的基本模型如下:

nn 个元素,第 ii 个元素有 ai,bi,cia_i, b_i, c_i 三个属性,设 f(i)f(i) 表示满足 ajaia_j \leq a_ibjbib_j \leq b_icjcic_j \leq c_ijij \ne ijj 的数量。给定一个或多个 xx,求 f(x)f(x) 的值。

#实现

CDQ 分治解决这类问题的算法流程如下:

  1. 找到这个序列的中点 mid\mathit{mid}
  2. 将所有点对 (i,j)(i, j) 划分为 3 类:
    1. 1i,jmid1 \leq i, j \leq \mathit{mid} 的点对;
    2. 1imid,mid+1jn1 \leq i \leq \mathit{mid}, \mathit{mid} + 1 \leq j \leq n 的点对;
    3. mid+1i,jn\mathit{mid} + 1 \leq i, j \leq n 的点对。
  3. (1,n)(1, n) 这个序列拆成两个序列 (1,mid)(1, \mathit{mid})(mid+1,n)(\mathit{mid} + 1, n)。此时第一类点对和第三类点对都在这两个序列之中;
  4. 递归地处理这两类点对;
  5. 设法处理第二类点对。

可以看到 CDQ 分治的思想就是不断地把点对通过递归的方式分给左右两个区间。

在实际应用时,我们通常使用一个函数 solve(l, r) 处理 li,jrl \leq i, j \leq r 的点对。上述算法流程中的递归部分便是通过 solve(l, mid)solve(mid, r) 来实现的。剩下的第二类点对则需要额外设计算法解决。

#代码

对应题目:P3810 【模板】三维偏序(陌上花开)

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <algorithm>
#include <iostream>

using std::cin;
using std::cout;
const char endl = '\n';

const int N = 1e5 + 5,
K = 2e5 + 5;

int n, k, ans[K];
int c[K];

struct node {
int a, b, c, cnt, res;

node()
: a(0), b(0), c(0), cnt(0), res(0) {}

node(int _a, int _b, int _c)
: a(_a), b(_b), c(_c), cnt(1), res(0) {}

bool operator<(const node& x) const {
return a == x.a ? b == x.b ? c < x.c : b < x.b : a < x.a;
}

bool operator==(const node& x) const {
return a == x.a && b == x.b && c == x.c;
}
} q[N], w[N];

inline int lowbit(int x) {
return x & -x;
}

void add(int x, int y) {
for (; x <= 2e5; x += lowbit(x)) c[x] += y;
}

int sum(int x) {
int res = 0;
for (; x; x -= lowbit(x)) res += c[x];
return res;
}

void solve(int l, int r) {
if (l >= r) return;

int mid = l + r >> 1;

// 分治
solve(l, mid);
solve(mid + 1, r);

// 双指针处理第二维
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r) {
if (q[i].b <= q[j].b) {
// 树状数组处理第三维
add(q[i].c, q[i].cnt);
w[++k] = q[i++];
} else {
q[j].res += sum(q[j].c);
w[++k] = q[j++];
}
}

// 补齐剩余部分
while (i <= mid) {
add(q[i].c, q[i].cnt);
w[++k] = q[i++];
}

while (j <= r) {
q[j].res += sum(q[j].c);
w[++k] = q[j++];
}

// 恢复原状
for (int i = l; i <= mid; i++) add(q[i].c, -q[i].cnt);
for (int i = l, j = 1; j <= k; i++, j++) q[i] = w[j];
}

int main() {
std::ios::sync_with_stdio(false);

cin >> n >> k;

for (int i = 1, a, b, c; i <= n; i++) {
cin >> a >> b >> c;

q[i] = node(a, b, c);
}

// 排序处理第一维
std::sort(q + 1, q + 1 + n);

int k = 1;
for (int i = 2; i <= n; i++) {
if (q[i] == q[k]) {
q[k].cnt++;
} else {
q[++k] = q[i];
}
}

// 分治
solve(1, k);

// 计算答案
for (int i = 1; i <= k; i++) {
ans[q[i].res + q[i].cnt - 1] += q[i].cnt;
}

for (int i = 0; i < n; i++) {
cout << ans[i] << endl;
}

return 0;
}

#参考资料

  1. 从《Cash》谈一类分治算法的应用文件存档备份,陈丹琦,2008 年。
  2. 2.15 CDQ 分治,AcWing 算法进阶课,闫学灿,2020 年 11 月 20 日。
  3. CDQ 分治,OI Wiki,2021 年 10 月 11 日。