cdq分治学习笔记
\(cdq\) 分治是一种思想而不是具体的算法,依原理与写法的不同,大致分为三类:
-
解决和点对有关的问题。
-
1D 动态规划的优化与转移。
-
通过 \(cdq\) 分治,将一些动态问题转化为静态问题。
解决和点对有关的问题
这类问题多数类似于「给定一个长度为 \(n\) 的序列,统计有一些特性的点对 \((i, j)\) 的数量」或「给定一个长度为n的序列,找到一对点 \((i, j)\) 使得一些函数的值最大」
\(cdq\) 分治解决这类问题的算法流程如下:
1.找到序列中点 \(mid\)(def : 集合 \(L =\) { \(x ∈ N \space | \space 1 \leq x \leq mid\) }, \(R =\) { \(x ∈ N \space | \space mid + 1 \leq x \leq n\) })
2.将 \((i,j)\) 分成3类:
a.\(i, j∈L\)
b.\(i∈L, j∈R\)
c.\(i,j∈R\)
(补充:显然有题目限制 \(i \leq j\))
3.将序列 \((1, n)\) 拆成 \((1, mid)\) 和 \((mid + 1, n)\) 两部分,此时a类点对和c类点对都在这两个序列之中,于是考虑递归处理这两类点对。
4.设法处理b类点对。
其实这种思想本质就是不断递归成左右两个区间处理,而夹在中间的b类点对则需要其他算法实现处理。
例题
【模板】三维偏序(陌上花开)说到 \(cdq\) 分治怎么能忘记我们可爱の陌上开花呢(Ciallo~(∠・ω< )⌒☆)
题意简述
有 $ 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 $ 的数量( \(d\) \(from\) \(0\) \(to\) \(n\) )。
思路
首先我们可以对第一维 \(a\) 进行排序,这样序列后面的数一定大于序列前面的数,我们就可以少处理一维。
接着,我们在处理第二维的时候进行 \(cdq\) 分治。
对于一个 \([l,r]\) 的区间,我们可以如此做:
-
首先处理 \([l,mid]\) 和 \((mid,r]\)。(\(mid\) 是区间中点)
- 如果 \(l=r\),那么一定没有贡献,可以直接返回。
-
然后,我们处理左端点在 \([l,mid]\),右端点在 \((mid,r]\) 的点对的贡献。
-
由于我们已经按 \(a\) 排好序,所以右区间 \((mid,r]\) 的点的 \(a\) 值一定大于左区间。
-
此时我们把两个区间分别按照 \(b\) 排序。
- 这样虽然会打乱区间内 \(a\) 的顺序,但是我们只考虑跨区间的贡献。而且区间内的贡献我们在前面已经算完了,后面只会算更大块区间的两侧贡献,而大块之间的 \(a\) 的顺序并不会被排序影响。
-
然后我们用树状数组+双指针法处理 \(c\) 的偏序关系。
-
由于我们只要算跨区间的贡献(\(b_i,c_i\) 都小于 \(b_j,c_j\),且 \(i\) 在左区间、\(j\) 在右区间的点对 \(i,j\) 个数),所以树状数组上第 \(i\) 位存 \(c_x=i\) 的 \(x\) 在左区间出现了多少次。(需要离散化)
-
我们两个指针 \(x,y\) 分别扫左区间和右区间。当 \(y\) 增加后,我们把满足 \(b_x \le b_y\) 的 \(c_x\) 都加入树状数组。由于 \(b\) 是有序的,所以可以用一个指针扫。等到所有满足条件的 \(c_x\) 都加入树状数组后,我们只需查询树状数组上 \(1 \sim c_y\) 的前缀和即是右端点为 \(y\) 的贡献。
-
-
-
然后我们退回上一层,继续这样操作,直到求出所有答案。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int n, k;
struct node{
int a, b, c, ans, same;
inline bool operator==(const node x)const{
return (x.a == a && x.b == b && x.c == c);
}
};
inline bool cmp1(node x, node y){
return (x.a == y.a) ? ((x.b == y.b) ? x.c < y.c : x.b < y.b) : x.a < y.a;
}
inline bool cmp2(node x, node y){
return (x.b == y.b) ? x.c < y.c : x.b < y.b;
}
node a[N], rd[N];
namespace BIT{
int t[N];
#define lowbit(x) (x & -x)
inline void add(int x, int v){
for(int i = x; i <= k; i += lowbit(i)) t[i] += v;
}
inline int get(int x){
int ret = 0;
for(int i = x; i; i -= lowbit(i)) ret += t[i];
return ret;
}
#undef lowbit
};
using namespace BIT;
inline void solve(int l, int r){
if(l == r) return ;
int mid = (l + r) >> 1;
solve(l, mid); solve(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].b <= a[j].b) add(a[i].c, a[i].same), i++;
a[j].ans += get(a[j].c);
j++;
}
for(int m = l; m < i; ++m) add(a[m].c, -a[m].same);
}
int ret[N];
signed main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> rd[i].a >> rd[i].b >> rd[i].c;
sort(rd + 1, rd + n + 1, cmp1);
int cnt = 0, d = n; n = 0;
for(int i = 1; i <= d; i++){
++cnt;
if(i < d && rd[i] == rd[i + 1]) continue;
a[++n] = rd[i];
a[n].same = cnt;
cnt = 0;
}
solve(1, n);
for(int i = 1; i <= n; i++) ret[a[i].ans + a[i].same - 1] += a[i].same;
for(int i = 0; i < d; i++) cout << ret[i] << "\n";
return 0;
}

浙公网安备 33010602011771号