CDQ 分治
本页面将介绍 CDQ 分治。
简介
CDQ 分治是一种思想而不是具体的算法,与 动态规划 类似。目前这个思想的拓展十分广泛,依原理与写法的不同,大致分为三类:
- 解决和点对有关的问题。
- 1D 动态规划的优化与转移。
- 通过 CDQ 分治,将一些动态问题转化为静态问题。
CDQ 分治的思想最早由 IOI2008 金牌得主陈丹琦在高中时整理并总结,它也因此得名。[^ref1]
解决和点对有关的问题
这类问题多数类似于「给定一个长度为 n 的序列,统计有一些特性的点对
的数量/找到一对点
使得一些函数的值最大」。
CDQ 分治解决这类问题的算法流程如下:
-
找到这个序列的中点
;
-
将所有点对
划分为 3 类:
的点对;
的点对;
的点对。
-
将
这个序列拆成两个序列
和
。此时第一类点对和第三类点对都在这两个序列之中;
-
递归地处理这两类点对;
-
设法处理第二类点对。
可以看到 CDQ 分治的思想就是不断地把点对通过递归的方式分给左右两个区间。
在实际应用时,我们通常使用一个函数 solve(l,r)
处理
的点对。上述算法流程中的递归部分便是通过 solve(l,mid)
与 solve(mid,r)
来实现的。剩下的第二类点对则需要额外设计算法解决。
例题
三维偏序
给定一个序列,每个点有
三个属性,试求:这个序列里有多少对点对
满足
且
且
且
。
解题思路
三维偏序是 CDQ 分治的经典问题。
题目要求统计序列里点对的个数,那试一下用 CDQ 分治。
首先将序列按
排序。
假设我们现在写好了 solve(l,r)
,并且通过递归搞定了 solve(l,mid)
和 solve(mid+1,r)
。现在我们要做的,就是统计满足
,
的点对
中,有多个点对还满足
,
,
的限制条件。
稍微思考一下就会发现,那个
的限制条件没啥用了:已经将序列按
排序,则
可转化为
。既然
比
小,
比
大,那
肯定比
要小。现在还剩下两个限制条件:
与
, 根据这个限制条件我们就可以枚举
, 求出有多少个满足条件的
。
为了方便枚举,我们把
和
中的点全部按照
的值从小到大排个序。之后我们依次枚举每一个
, 把所有
的点
全部插入到某种数据结构里(这里我们选择 树状数组)。此时只要查询树状数组里有多少个点的
值是小于
的,我们就求出了对于这个点
,有多少个
可以合法匹配它了。
当我们插入一个
值等于
的点时,我们就令树状数组的
这个位置单点 + 1,而查询树状数组里有多少个点小于
的操作实际上就是在求 前缀和,只要我们事先对于所有的
值做了 离散化,我们的复杂度就是对的。
对于每一个
,我们都需要将所有
的点
插入树状数组中。由于所有的
和
都已事先按照
值排好序,这样的话只要以双指针的方式在树状数组里插入点,则对树状数组的插入操作就能从
次降到
次。
通过这样一个算法流程,我们就用
的时间处理完了关于第二类点对的信息了。此时算法的时间复杂度是
。
示例代码
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 | #include <algorithm>
#include <iostream>
constexpr int MAXN = 1e5 + 10;
constexpr int MAXK = 2e5 + 10;
int n, k;
struct Element {
int a, b, c;
int cnt;
int res;
bool operator!=(Element other) const {
if (a != other.a) return true;
if (b != other.b) return true;
if (c != other.c) return true;
return false;
}
};
Element e[MAXN];
Element ue[MAXN];
int m, t;
int res[MAXN];
struct BinaryIndexedTree {
int node[MAXK];
int lowbit(int x) { return x & -x; }
void Add(int pos, int val) {
while (pos <= k) {
node[pos] += val;
pos += lowbit(pos);
}
return;
}
int Ask(int pos) {
int res = 0;
while (pos) {
res += node[pos];
pos -= lowbit(pos);
}
return res;
}
} BIT;
bool cmpA(Element x, Element y) {
if (x.a != y.a) return x.a < y.a;
if (x.b != y.b) return x.b < y.b;
return x.c < y.c;
}
bool cmpB(Element x, Element y) {
if (x.b != y.b) return x.b < y.b;
return x.c < y.c;
}
void CDQ(int l, int r) {
if (l == r) return;
int mid = (l + r) / 2;
CDQ(l, mid);
CDQ(mid + 1, r);
std::sort(ue + l, ue + mid + 1, cmpB);
std::sort(ue + mid + 1, ue + r + 1, cmpB);
int i = l;
int j = mid + 1;
while (j <= r) {
while (i <= mid && ue[i].b <= ue[j].b) {
BIT.Add(ue[i].c, ue[i].cnt);
i++;
}
ue[j].res += BIT.Ask(ue[j].c);
j++;
}
for (int k = l; k < i; k++) BIT.Add(ue[k].c, -ue[k].cnt);
return;
}
using std::cin;
using std::cout;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> e[i].a >> e[i].b >> e[i].c;
std::sort(e + 1, e + n + 1, cmpA);
for (int i = 1; i <= n; i++) {
t++;
if (e[i] != e[i + 1]) {
m++;
ue[m].a = e[i].a;
ue[m].b = e[i].b;
ue[m].c = e[i].c;
ue[m].cnt = t;
t = 0;
}
}
CDQ(1, m);
for (int i = 1; i <= m; i++) res[ue[i].res + ue[i].cnt - 1] += ue[i].cnt;
for (int i = 0; i < n; i++) cout << res[i] << '\n';
return 0;
}
|
CQOI2011 动态逆序对
对于序列
,它的逆序对数定义为集合
中的元素个数。
现在给出
的一个排列,按照某种顺序依次删除
个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。
示例代码
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 | // 仔细推一下就是和三维偏序差不多的式子了,基本就是一个三维偏序的板子
#include <algorithm>
#include <iostream>
using namespace std;
using ll = long long;
int n;
int m;
struct treearray {
int ta[200010];
void ub(int& x) { x += x & (-x); }
void db(int& x) { x -= x & (-x); }
void c(int x, int t) {
for (; x <= n + 1; ub(x)) ta[x] += t;
}
int sum(int x) {
int r = 0;
for (; x > 0; db(x)) r += ta[x];
return r;
}
} ta;
struct data_ {
int val;
int del;
int ans;
} a[100010];
int rv[100010];
ll res;
// 重写两个比较
bool cmp1(const data_& a, const data_& b) { return a.val < b.val; }
bool cmp2(const data_& a, const data_& b) { return a.del < b.del; }
void solve(int l, int r) { // 底下是具体的式子,套用
if (r - l == 1) {
return;
}
int mid = (l + r) / 2;
solve(l, mid);
solve(mid, r);
int i = l + 1;
int j = mid + 1;
while (i <= mid) {
while (a[i].val > a[j].val && j <= r) {
ta.c(a[j].del, 1);
j++;
}
a[i].ans += ta.sum(m + 1) - ta.sum(a[i].del);
i++;
}
i = l + 1;
j = mid + 1;
while (i <= mid) {
while (a[i].val > a[j].val && j <= r) {
ta.c(a[j].del, -1);
j++;
}
i++;
}
i = mid;
j = r;
while (j > mid) {
while (a[j].val < a[i].val && i > l) {
ta.c(a[i].del, 1);
i--;
}
a[j].ans += ta.sum(m + 1) - ta.sum(a[j].del);
j--;
}
i = mid;
j = r;
while (j > mid) {
while (a[j].val < a[i].val && i > l) {
ta.c(a[i].del, -1);
i--;
}
j--;
}
sort(a + l + 1, a + r + 1, cmp1);
return;
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i].val;
rv[a[i].val] = i;
}
for (int i = 1; i <= m; i++) {
int p;
cin >> p;
a[rv[p]].del = i;
}
for (int i = 1; i <= n; i++) {
if (a[i].del == 0) a[i].del = m + 1;
}
for (int i = 1; i <= n; i++) {
res += ta.sum(n + 1) - ta.sum(a[i].val);
ta.c(a[i].val, 1);
}
for (int i = 1; i <= n; i++) {
ta.c(a[i].val, -1);
}
solve(0, n);
sort(a + 1, a + n + 1, cmp2);
for (int i = 1; i <= m; i++) {
cout << res << '\n';
res -= a[i].ans;
}
return 0;
}
|
CDQ 分治优化 1D/1D 动态规划的转移
相关内容:CDQ 分治优化 DP
1D/1D 动态规划指的是一类特定的 DP 问题,该类题目的特征是 DP 数组是一维的,转移是
的。如果条件良好的话,有时可以通过 CDQ 分治来把它们的时间复杂度由
降至
。
例如,给定一个序列,每个元素有两个属性
,
。我们希望计算一个 DP 式子的值,它的转移方程如下:
$dp_{i}=1+ \max_{j=1}^{i-1}dp_{j}[a_{j}
本页面最近更新:2025/8/5 18:12:34,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:Ir1d, H-J-Granger, NachtgeistW, StudyingFather, countercurrent-time, hsfzLZH1, CCXXXI, Early0v0, Enter-tainer, Tiphereth-A, AngelKitty, Chrogeek, cjsoft, diauweb, ezoixx130, GekkaSaori, Konano, LovelyBuggies, lyccrius, Makkiy, mgt, minghu6, P-Y-Y, PotassiumWings, rtxu, SamZhangQingChuan, sshwy, Suyun514, weiyong1024, 1804040636, abc1763613206, Backl1ght, c-forrest, flylai, GavinZhengOI, Gesrua, Great-designer, i207M, kenlig, kxccc, Luckyblock233, lychees, ouuan, Peanut-Tang, Planet6174, shadowice1984, Siyuan, SukkaW, Xarfa, Xeonacid, ylxmf2005
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用