CDQ 分治
写在前面
学完分块,跑来学 CDQ 分治,导致 qbxt 导学的题没做完,然后就,挨老吕批了 qwq
可恶的 ycc 和 myz(偷偷往题单里放题 = =)
本文主要借 (chao) 鉴 (xi) 自 CDQ 分治 ,也有自己的想法
如果有什么不对的地方,可以直接看原文
引子
关于 CDQ 分治
它不是一种算法,而是一种思想,在 \(OI\) 中可以把它分为三类
- cdq 分治解决和点对有关的问题
- cdq 分治优化 1D/1D 动态规划的转移
- 通过 cdq 分治,将一些动态问题转化为静态问题
CDQ 分治解决和点对有关的问题
给你一个长度为 \(n\) 的序列,统计有一些特性的点对有多少个,又或者说是找到一对点使得一些函数的值最大之类的
基本流程
1.找序列的中点
2.把所有的点对 \((i,j)\) 分为三类
第一种:\(1\leq i \leq mid,~~1 \leq j \leq mid\)
第二种:\(1\leq i \leq mid,~~mid + 1 \leq j\leq n\)
第三种: \(mid + 1\leq i \leq n,~~mid + 1\leq j \leq n\)
3.把 \((1,n)\) 这个序列拆成两部分,对于第一种和第三种可以直接用递归方式求解
4.处理第二种情况
三维偏序
给定一个序列,每个点有两个属性 \((a,b)\),试求:这个序列里有多少对点对 \((i, j)\) 满足 \(i < j,a_i < a_j, b_i < b_j\)
统计点对的个数,二话不说,直接上 CDQ
假设当前解决要解决的区间为 \([l,r]\)
根据上面所说 \([l, mid], [mid + 1, r]\) 我们已经都通过递归求出来了,现在要解决的就是上面的第二种情况:
也就是统计满足 \(l \leq i \leq mid\) ,\(mid + 1 \leq j \leq r\) 的点对 \((i,j)\) 有多少个还满足上面的条件
很显然第一个条件它一定满足,考虑剩下那俩条件 \(a_i < a_j\) ,\(b_i < b_j\)
首先按照 \(a_i\) 的值对区间 \([i, mid]\) 和 \([mid + 1,~j]\) 从小到大排个序,然后枚举 \(j\) ,把所有的 \(a_i < a_j\) 的 \(i\) 点的 \(b\) 属性插入到树状数组中(如果 \(i\) 点的 b 属性为 x 那么 x 这个点就 + 1),那么求小于 \(b_j\) 的点有多少个相当于就是求个前缀和了
因为 \(a\) 是排好序的,所以可以用一个双指针把它们插入树状数组中去,复杂度为 \(O(n)\)
这个操作总的时间复杂度为 \(O(n log~n)\)
总的复杂度: \(O(n~log^2n)\)
板子题
/*
work by: Ariel
*/
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 200005;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
struct node{int a, b, c, cnt, ans;}s1[N], s2[N];
int n, m, k, tree[N << 1], tot, cnt, Ans[N];
bool cmp1(node x, node y) {
if (x.a == y.a) {
if (x.b == y.b) return x.c < y.c;
else return x.b < y.b;
}
return x.a < y.a;
}
bool cmp2(node x, node y) {
if (x.b == y.b) return x.c < y.c;
return x.b < y.b;
}
void update(int x, int y) {
for (int i = x; i <= k; i += i & (-i)) tree[i] += y;
}
int query(int x) {
int ans = 0;
for (int i = x; i; i -= i & (-i)) ans += tree[i];
return ans;
}
void CDQ(int l, int r) {
if (l == r) return ;
int mid = (l + r) >> 1;
CDQ(l, mid), CDQ(mid + 1, r);
sort(s2 + l, s2 + mid + 1, cmp2), sort(s2 + mid + 1, s2 + r + 1, cmp2);
int i = l;
for (int j = mid + 1; j <= r; j++) {
while (s2[j].b >= s2[i].b && i <= mid) {
update(s2[i].c, s2[i].cnt);
i++;
}
s2[j].ans += query(s2[j].c);
}
for (int o = l; o < i; o++) update(s2[o].c, -s2[o].cnt);//树状数组清空
}
int main() {
n = read(), k = read();
for (int i = 1, a, b, c; i <= n; i++) {
a = read(), b = read(), c = read();
s1[i].a = a, s1[i].b = b, s1[i].c = c;
}
sort(s1 + 1, s1 + n + 1, cmp1);
for (int i = 1; i <= n; i++) {
tot++;
if (s1[i].a != s1[i + 1].a || s1[i].b != s1[i + 1].b || s1[i].c != s1[i + 1].c) {
s2[++m].a = s1[i].a, s2[m].b = s1[i].b, s2[m].c = s1[i].c, s2[m].cnt = tot;
tot = 0;
}
}
CDQ(1, m);
for (int i = 1; i <= m; i++) Ans[s2[i].ans + s2[i].cnt - 1] += s2[i].cnt;
for (int i = 0; i < n; i++) printf("%d\n", Ans[i]);
}

浙公网安备 33010602011771号