【Coel.学习笔记】【半途跑路】CDQ 分治
作者:互联网
最近在刷状压 DP,结果发现太难不会做,跑来学点别的。
反正 CSP-S2 之前刷完就行了,吧?
放在数据结构里面是因为 CDQ 分治和数套树能解决的问题差不多,所以放了进去(绝不是因为懒得开一个“离线算法”的 Tag!)
引入
CDQ 分治是一种通过把动态询问/点对问题等离线处理,并分治求解的算法。这种思想最早于 IOI2008 国家集训队选手陈丹琦在其论文 从《Cash》谈一类分治算法的应用 中总结,故得名。
例题讲解
虽然原论文给出的例题是[NOI2007] 货币兑换,但我们先从另一个经典题目——三维偏序讲起。
【模板】三维偏序
洛谷传送门
有 $ 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-1] $,求 $ f(i) = d $ 的数量。(这里描述用 \(n-1\) 代替右开)
解析:这题有很多做法,如树套树、K-D Tree。这里当然只介绍 CDQ 分治的做法。
假设只有一个属性,即对每个 \(i\) 找到 \(a_j\leq a_i\) 的数量。显然我们只要按照 \(a_i\) 大小排序,那么对于 \(a_i\) 就有 \(i-1\) 个数字比它小。
当属性有两个的时候,同样按照 \(a_i\) 排序,然后从前往后扫描。那么对于 \(i\) 前面的元素 \(j\) 一定有 \(a_j\leq a_i\),任务就变成找 \(b_j\leq b_i\) 的数量。一种办法是先把 \(b\) 离散化,然后用类似树状数组求逆序对的方式求解即可。
当然也可以像归并排序求逆序对一样,先做排序,然后根据 \(i,j\) 在两个分治区间内的情况分类讨论:
- 若 \(i,j\) 均在左区间,则对左区间分治求解;
- 若 \(i,j\) 均在右区间,则对右区间分治求解;
- 若 \(i,j\) 分别在左、右区间(显然 \(i\) 一定在 \(j\) 右边),那么对于左区间的任何一个元素,寻找 \(b_j\leq b_i\) 的数量即可。由于分治同时会把 \(b\) 排序,所以对 \(i,j\) 各开一个指针,双指针扫描一遍就可以求出答案。
这样每次分治都要做一次 \(O(n)\) 的双指针,总复杂度为 \(O(n\log n)\)。
现在来到三维。类比上面提到的归并排序方法,先做一遍排序。那么对于每个 \(i\),满足条件的 \(j\) 一定在左边。同样,我们对 \(i,j\) 的情况做分类讨论:同在左区间,同在右区间,分别位于两个区间。前两个分治即可,重点看第三个。
由于做了排序,所以 \(a_j\leq a_i\) 一定满足。 接下来对于 \(b_j\leq b_i\),用双指针寻找第一个 \(b_j> b_i\) 的位置,那么每个 \(i\) 都可以用 \(O(n)\) 找到对应的 \(j\),即答案在左区间边界到 \(j-1\) 的范围内。最后对于 \(c_j\leq c_i\),用树状数组就可求出。
这样每次分治都要做 \(O(n)\) 双指针,且每次移动指针都要用 \(O(\log n)\) 的树状数组维护答案,故总时间复杂度为 \(O(n\log ^2 n)\)。
总结一下,CDQ 分治的本质其实就是消除偏序维度。对于一维,直接排序;对于二维。在一维的基础上做树状数组/分治;对于三维,再在二维基础上加。从某种程度上说,CDQ 分治也可以看作归并排序一类分治的扩展算法。顺带一提,树套树求三维偏序的本质也是消除维度,不过是用权值线段树代替分治、支持强制在线罢了。
#include<cctype>
#include<cstdio>
#include<algorithm>
#include<cstring>
struct __Coel_FastIO {
#ifndef LOCAL
#define _getchar_nolock getchar_unlocked
#define _putchar_nolock putchar_unlocked
#endif
inline __Coel_FastIO& operator>>(int& x) {
x = 0;
bool f = false;
char ch = _getchar_nolock();
while (!isdigit(ch)) {
if (ch == '-') f = true;
ch = _getchar_nolock();
}
while (isdigit(ch)) {
x = x * 10 + ch - '0';
ch = _getchar_nolock();
}
if (f) x = -x;
return *this;
}
inline __Coel_FastIO& operator<<(int x) {
if (x < 0) {
x = -x;
_putchar_nolock('-');
}
static int buf[35];
int top = 0;
do {
buf[top++] = x % 10;
x /= 10;
} while (x);
while (top) _putchar_nolock(buf[--top] + '0');
return *this;
}
inline __Coel_FastIO& operator<<(char x) {
return _putchar_nolock(x), *this;
}
} qwq;
const int maxn = 2e5 + 10;
int n, m, top = 1;
int ans[maxn];
struct node {
int a, b, c;
int res, cnt; //记录出现次数,处理属性相等的状态
bool operator<(const node &x) const {
if (a != x.a) return a < x.a;
if (b != x.b) return b < x.b;
return c < x.c;
}
bool operator==(const node &x) const {
return a == x.a && b == x.b && c == x.c;
}
} q[maxn], tem[maxn];
class Fenwick_Tree {
private:
#define lowbit(x) (x & (-x))
int c[maxn];
public:
void add(int x, int v) {
for (int i = x; i < maxn; i += lowbit(i)) c[i] += v;
}
int query(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += c[i];
return res;
}
} T;
void CDQ_Divide(int l, int r) {
if (l >= r) return;
int mid = (l + r) >> 1;
CDQ_Divide(l, mid), CDQ_Divide(mid + 1, r);
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r) //两个区间都没遍历完时
if (q[i].b <= q[j].b) //不满足条件,把信息存入树状数组
T.add(q[i].c, q[i].cnt), tem[k++] = q[i++];
else // 满足条件,在树状数组上得到答案
q[j].res += T.query(q[j].c), tem[k++] = q[j++];
while (i <= mid) T.add(q[i].c, q[i].cnt), tem[k++] = q[i++];
while (j <= r) q[j].res += T.query(q[j].c), tem[k++] = q[j++]; //将没有处理完的部分继续处理
for (i = l; i <= mid; i++) T.add(q[i].c, -q[i].cnt); //还原树状数组
for (i = l, j = 0; j < k; i++, j++) q[i] = tem[j]; //将归并排序后结果复制到原数据中
}
int main(void) {
qwq >> n >> m;
for (int i = 0; i < n; i++)
qwq >> q[i].a >> q[i].b >> q[i].c, q[i].cnt = 1;
std::sort(q, q + n);
for (int i = 1; i < n; i++) { // 特判属性相等时的答案
// 这里的 top 其实也起到了去重的作用
if (q[i] == q[top - 1]) q[top - 1].cnt++;
else q[top++] = q[i];
}
CDQ_Divide(0, top - 1);
for (int i = 0; i < top; i++)
ans[q[i].res + q[i].cnt - 1] += q[i].cnt;
for (int i = 0; i < n; i++)
qwq << ans[i] << '\n';
return 0;
}
标签:ch,半途,分治,Coel,leq,int,CDQ,排序 来源: https://www.cnblogs.com/Coel-Flannette/p/16670766.html