CDQ 分治的思想最早由 IOI2008 金牌得主陈丹琦在高中时整理并总结,因此得名。
适用范围
多用于统计有特征的点对 \((i,j)\) 的数量或找出有特征的点对。
流程
对于一个区间 \([L,R]\) 中的点对 \((i,j)\)(\(L\le i<j\le R\)),考虑分为三部分(\(mid=\frac{L+R}2\)):
- \(L\le i<j\le mid\),\(mid+1\le i<j \le R\):将 \([L,R]\) 拆为 \([L,mid],[mid+1,R]\),发现这两部分点对分别位于这两个区间中,递归处理这两部分。
- \(L\le i\le mid,mid+1\le j\le R\):设法处理这些点对。
在实现上,通常设一个 solve(L,R)
函数来处理在 \([L,R]\) 区间内的点对:
void solve (int L, int R) {
int mid = (L + R) >> 1; if (L == R) return ; // 边界
solve (L, mid); solve (mid + 1, R); // 递归处理更小的区间
... // 处理跨越 mid 的点对
}
例题
三维偏序
solution
考虑使用 CDQ 分治。
将点按照 \(a_i\) 排序,对于一个区间 \([L,R]\),假设我们已经处理好了 \([L,mid],[mid+1,R]\) 内的点对,现在考虑处理跨越 \(mid\) 的点对。
对于点对 \((i,j)\)(\(i\le mid<j\)),因为我们已经按照 \(a_i\) 排序,所以 \(a_i\le a_j\) 就只要求 \(i\le j\),因为 \(i,j\) 分居 \(mid\) 两侧,所以这个限制显然满足。
我们把 \([L,mid],[mid+1,R]\) 区间内的点分别按照 \(b_i\) 排序。然后对于每一个 \(j\),考虑求出有多少 \(i\) 满足 \(b_i\le b_j\) 的限制条件。不难发现,在 \(j\) 逐渐向右移时,满足 \(b_i\le b_j\) 的 \(i\) 的区间右端点是不减的(区间左端点显然为 \(L\)),因此我们把 \(i,j\) 看作双指针,就可以线性维护。在 \(i\) 向右移动时,我们将经过的点的 \(c_i\) 插入树状数组,对于每一个 \(j\),只要查询有多少 \(c_i\le c_j\) 即可,这样就可以找到合法的点对。
时间复杂度 \(T(n)=2T(\left\lceil\frac n2\right\rceil)+\mathcal{O}(n\log n)=\mathcal{O}(n\log^2 n)\)
code
CI N = 1e5; struct node {int a; int b; int c;} p[N + 5], m[N + 5]; struct llsAKIOI {int b; int c; int id;} q[N + 5]; int n, K, BIT[N * 2 + 5], ans[N + 5], Tng[N + 5], dis[N + 5];
int lowbit (int x) {return x & -x;} void add (int id, int x) {for (; id <= K; id += lowbit (id)) BIT[id] += x;}
int query (int id) {int s = 0; for (; id; id -= lowbit (id)) s += BIT[id]; return s;}
bool cmp (node x, node 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 cmp2 (llsAKIOI x, llsAKIOI y) {return x.b == y.b ? x.c < y.c : x.b < y.b;}
void solve (int L, int R) {
RI i, j; if (L == R) return ; int mid = (L + R) >> 1; solve (L, mid); solve (mid + 1, R); for (i = L; i <= R; ++ i) q[i].b = p[i].b, q[i].c = p[i].c, q[i].id = i;
sort (q + L, q + mid + 1, cmp2); sort (q + mid + 1, q + R + 1, cmp2); i = L; for (j = mid + 1; j <= R; ++ j) {
W (i <= mid && q[i].b <= q[j].b) add (q[i].c, dis[q[i].id]), ++ i; ans[q[j].id] += query (q[j].c);
} W (i > L) -- i, add (q[i].c, - dis[q[i].id]);
}
int main () {
RI i, j; for (Read (n, K), i = 1; i <= n; ++ i) Read (m[i].a, m[i].b, m[i].c); sort (m + 1, m + n + 1, cmp);
int cnt = 0, tme = 0; for (i = 1; i <= n; ++ i) {
++ tme; if (m[i].a != m[i + 1].a || m[i].b != m[i + 1].b || m[i].c != m[i + 1].c) {
++ cnt; p[cnt] = m[i]; dis[cnt] = tme; tme = 0;
}
} solve (1, cnt);
for (i = 1; i <= cnt; ++ i) Tng[ans[i] + dis[i] - 1] += dis[i]; for (i = 0; i < n; ++ i) printf ("%d\n", Tng[i]); printf ("\n");
return 0;
}