CDQ分治
CDQ 分治的思想最早由 IOI2008 金牌得主陈丹琦在高中时整理并总结,它也因此得名。
CDQ分治的思想常用于离线解决点对之间的问题,最经典的如三维偏序。
解决这类问题的过程为:
-
将序列 $ (l, r) $ 分为 $ (l, mid) $ 和 $ (mid + 1, r) $ 。
-
递归处理序列$ (l, mid) $ 和 $ (mid + 1, r) $中的答案。
-
设计算法处理对于点对 $ (i, j) (l \le i \le mid), (mid + 1 \le j \le r) $ 间的答案。
$ \color {#9d3dcf}P3810 \text【模板】三维偏序(陌上花开)$
题意
有 $ n $ 个元素,第 $ i $ 个元素有 $ a_i,b_i,c_i $ 三个属性,设 $ f(i) $ 表示满足 $ a_j \leq a_i $ 且 $ b_j \leq b_i $ 且 $ c_j \leq c_i $ 且 $ j \ne i $ 的 \(j\) 的数量。
对于 $ d \in [0, n) $,求 $ f(i) = d $ 的数量。
思路
考虑先将数组按 $ a $ 排序,然后开始分治。
假设当前在处理区间 \(l, r\) ,并且已经统计完$ (l, mid) $ 和 $ (mid + 1, r) $中的答案,那么就要思考如何处理点对 $ (i, j) (l \le i \le mid), (mid + 1 \le j \le r) $ 间的答案。
由于开始时已将数组按 $ a $ 排序,所以左区间内的所有点的 $ a $ 都小于右区间。
那么对于 $ j \in [mid + 1, r] $ , $ f(j) $ 应加上 $ b_i \le b_j $ && $ c_i \le c_j $ $ (i \in [l, mid]) $ 的 $ i $ 的数量。
为了处理第二维 $ b $ ,我们可以将区间都按 $ b $ 排序,然后使用双指针维护 $ b_i \le b_j $。
维护第三维 $ c $ , 我们可以使用树状数组。
在 $ i $ 指针向右移的过程中,我们把树状数组中 $ c_i $ 位上 $ +1 $,这样更新 $ f(j) $ 时只需在树状数组中查询 $ \le c_j $ 的个数即可。
注意:进入递归前需要对原数组进行去重,否则会出错。
代码
#include<bits/stdc++.h>
#define int long long
#define Debug puts("Oops!")
using namespace std;
const int N = 2e5 + 5, M = 5e5 + 5;
int n, m, k;
int res[N];
struct sb {
int x, y, z;
int cnt, f;
}p[N], a[N];
struct BIT {
int t[N];
int lowbit(int x) {return x & -x;}
void add(int x, int c) {for(; x <= k; x += lowbit(x)) t[x] += c;}
int ask(int x) {int res = 0; for(; x; x -= lowbit(x)) res += t[x]; return res;}
}T;
bool cmp1(sb x, sb y) {
if(x.x != y.x) return x.x < y.x;
if(x.y != y.y) return x.y < y.y;
return x.z < y.z;
}
bool cmp2(sb x, sb y) {
if(x.y != y.y) return x.y < y.y;
return x.z < y.z;
}
void cdq(int l, int r) {
if(l == r) return ;
int mid = l + r >> 1;
cdq(l, mid), cdq(mid + 1, r);
sort(a + l, a + mid + 1, cmp2), sort(a + mid + 1, a + r + 1, cmp2);
int i = l, j = mid + 1;
while(j <= r) {
while(i <= mid && a[i].y <= a[j].y) T.add(a[i].z, a[i].cnt), i++;
a[j].f += T.ask(a[j].z);
j++;
}
for(int k = l; k < i; k++) T.add(a[k].z, -a[k].cnt);
}
inline int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
return x * f;
}
signed main() {
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
n = read(), k = read();
for(int i = 1; i <= n; i++) p[i].x = read(), p[i].y = read(), p[i].z = read();
sort(p + 1, p + 1 + n, cmp1);
int cnt = 0;
for(int i = 1; i <= n; i++) {
cnt++;
if(p[i].x != p[i + 1].x || p[i].y != p[i + 1].y || p[i].z != p[i + 1].z)
a[++m].x = p[i].x, a[m].y = p[i].y, a[m].z = p[i].z, a[m].cnt = cnt, cnt = 0;
}
cdq(1, m);
for(int i = 1; i <= m; i++) res[a[i].f + a[i].cnt] += a[i].cnt;
for(int i = 1; i <= n; i++) cout << res[i] << endl;
return 0;
}
$ \color{#9d3dcf} P4390 \text[BalkanOI2007] Mokia 摩基亚 $
题意
有四种命令,意义如下:
| 命令 | 参数 | 意义 |
|---|---|---|
| \(0\) | \(w\) | 初始化一个全零矩阵。本命令仅开始时出现一次。 |
| \(1\) | \(x\ y\ a\) | 向方格 \((x, y)\) 中添加 \(a\) 个用户。\(a\) 是正整数。 |
| \(2\) | \(x_1\ y_1\ x_2\ y_2\) | 查询 \(x_1\leq x\leq x_2\),\(y_1\leq y\leq y_2\) 所规定的矩形中的用户数量。 |
| \(3\) | 无参数 | 结束程序。本命令仅结束时出现一次。 |
对于 \(100\%\) 的数据,保证:
- \(1\leq w\leq 2\times 10 ^ 6\)。
- \(1\leq x_1\leq x_2\leq w\),\(1\leq y_1\leq y_2\leq w\),\(1\leq x,y\leq w\),\(0<a\leq 10000\)。
- 命令 \(1\) 不超过 \(160000\) 个。
- 命令 \(2\) 不超过 \(10000\) 个。
思路
首先想到的是二维树状数组,可惜矩阵大小高达 $ 2 \times 10 ^ 6 $ ,直接放弃。
设 $ S $ 为前缀和数组
显然,$ 2 $ 命令可以转化为四个操作:
求 $ S_{x_2, y_2} $, $ S_{x_2, y_1 - 1} $, $ S_{x_1 - 1, y_2} $, $ S_{x_1 - 1, y_1 - 1} $。
下文将求 $ S_{x, y} $ 称为查询操作。
可以发现如果某次 $ 1 $ 命令要对查询操作造成贡献,需要满足三个条件:
- $ 1 $ 命令出现时间比查询操作小
- $ 1 $ 命令中的 $ x $ 比查询操作中的 $ x $ 小
- $ 1 $ 命令中的 $ y $ 比查询操作中的 $ y $ 小
哦——,这不就是三维偏序板子嘛!
注意:求前缀和时可能出现 $ x = 0 $ 或 $ y = 0 $ 的情况,最方便的处理方法是将命令中的 $ x, y $ 均加一
矩阵边长也要加!!!
代码
#include<bits/stdc++.h>
// #define int long long
#define Debug puts("Oops!")
using namespace std;
const int N = 2e6 + 5, M = 5e5 + 5;
int n, m;
int res[N];
struct sb {
int x, y, z, f;
int cnt, res;
}a[N];
void add(int x, int y) {
a[++m].x = m, a[m].y = x, a[m].z = y, a[m].cnt = 0, a[m].f = 1;
}
struct BIT {
int t[N];
int lowbit(int x) {return x & -x;}
void add(int x, int c) {for(; x <= n; x += lowbit(x)) t[x] += c;}
int ask(int x) {int res = 0; for(; x; x -= lowbit(x)) res += t[x]; return res;}
}T;
bool cmp1(sb x, sb y) {
if(x.x != y.x) return x.x < y.x;
if(x.y != y.y) return x.y < y.y;
return x.z < y.z;
}
bool cmp2(sb x, sb y) {
if(x.y != y.y) return x.y < y.y;
return x.z < y.z;
}
void cdq(int l, int r) {
if(l == r) return ;
int mid = l + r >> 1;
cdq(l, mid), cdq(mid + 1, r);
sort(a + l, a + mid + 1, cmp2), sort(a + mid + 1, a + r + 1, cmp2);
int i = l, j = mid + 1;
while(j <= r) {
while(i <= mid && a[i].y <= a[j].y) T.add(a[i].z, a[i].cnt), i++;
if(a[j].f) a[j].res += T.ask(a[j].z);
j++;
}
for(int k = l; k < i; k++) T.add(a[k].z, -a[k].cnt);
}
inline int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
return x * f;
}
signed main() {
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
vector<int> q;
int op = read();
while(op != 3) {
if(op == 0) n = read() + 1;
else if(op == 1) a[++m].x = m, a[m].y = read() + 1, a[m].z = read() + 1, a[m].cnt = read();
else if(op == 2) {
int ax = read() + 1, ay = read() + 1, bx = read() + 1, by = read() + 1;
q.emplace_back(m + 1);
add(bx, by), add(bx, ay - 1), add(ax - 1, by), add(ax - 1, ay - 1);
}
op = read();
}
cdq(1, m);
sort(a + 1, a + 1 + m, cmp1);
for(int u : q) cout << a[u].res - a[u + 1].res - a[u + 2].res + a[u + 3].res << endl;
return 0;
}

浙公网安备 33010602011771号