专题:CDQ 分治
本页面将完整介绍 CDQ 分治。
简介
CDQ 分治是一种思想而不是具体的算法,与动态规划类似。目前这个思想的拓展十分广泛,依原理与写法的不同,大致分为三类:
- 解决和点对有关的问题。
- 1D 动态规划的优化与转移。
- 通过 CDQ 分治,将一些动态问题转化为静态问题。
CDQ 分治的思想最早由 IOI2008 金牌得主陈丹琦在高中时整理并总结,它也因此得名。
解决和点对有关的问题
这类问题多数类似于「给定一个长度为 nn 的序列,统计有一些特性的点对 (i,j)(i,j) 的数量/找到一对点 (i,j)(i,j) 使得一些函数的值最大」。
CDQ 分治解决这类问题的算法流程如下:
- 找到这个序列的中点 ;
- 将所有点对 (i,j)(i,j) 划分为 33 类:
- 1 \leq i \leq mid,1 \leq j \leq mid1≤i≤mid,1≤j≤mid 的点对;
- 1 \leq i \leq mid,mid +1 \leq j \leq n1≤i≤mid,mid+1≤j≤n 的点对;
- \operatorname{mid}+1 \leq i \leq n,mid +1 \leq j \leq nmid+1≤i≤n,mid+1≤j≤n 的点对。
- 将 (1,n)(1,n) 这个序列拆成两个序列 (1,mid)(1,mid) 和 (mid+1,n)(mid+1,n) 。此时第一类点对和第三类点对都在这两个序列之中;
- 递归地处理这两类点对;
- 设法处理第二类点对。
可以看到 CDQ 分治的思想就是不断地把点对通过递归的方式分给左右两个区间。
在实际应用时,我们通常使用一个函数 solve(l,\mathrm{r})solve(l,r) 处理 l \leq i \leq r,l \leq j \leq rl≤i≤r,l≤j≤r 的点对。上述算法流 程中的递归部分便是通过 solve(l,mid)solve(l,mid) 与 solve(mid,r)solve(mid,r) 来实现的。剩下的第二类点对则需要额外设计算法解决。
典型例题1:LOJ112/洛谷P3810 三维偏序(陌上开花)
分析:
三维偏序(陌上开花)是 CDQ 分治的经典问题。
假设我们现在写好了 solve (l, r)solve(l,r) ,并且通过递归搞定了 solve (l, mid)solve(l,mid) 和 solve(mid+1,r)solve(mid+1,r) 。现在我们要做的,就是统计满足 l \leq i \leq mid,mid+1 \leq j \leq rl≤i≤mid,mid+1≤j≤r 的点对 (i, j)(i,j) 中,有多个点对还满足 i<j,a_i<a_j,b_i<b_ji<j,ai<aj,bi<bj 的限制条件。
稍微思考一下就会发现,那个 i<ji<j 的限制条件没啥用了:既然 ii 比 midmid 小, jj 比 midmid 大,那 ii 肯定比 jj 要小。 现在还剩下两个限制条件: a_{i}<a_{j}ai<aj 与 b_{i}<b_{j}bi<bj , 根据这个限制条件我们就可以枚举 jj , 求出有多少个满足条件的 ii。
为了方便枚举,我们把 (l, mid)(l,mid) 和 (mid+1, r)(mid+1,r) 中的点全部按照 aa 的值从小到大排个序。之后我们依次枚举每一 个 jj , 把所有 a_{i}<a_{j}ai<aj 的点 ii 全部揷入到某种数据结构里(这里我们选择树状数组)。此时只要查询树状数组里有多少个点的 bb 值是小于 b_{j}bj 的,我们就求出了对于这个点 jj ,有多少个 ii 可以合法匹配它了。
当我们揷入一个 bb 值等于 xx 的点时,我们就令树状数组的 xx 这个位置单点 +1+1,而查询树状数组里有多少个点小于 xx 的操作实际上就是在求前缀和,只要我们事先对于所有的 bb 值做了离散化,我们的复杂度就是对的。
对于每一个 jj,我们都需要将所有 a_{i}<a_{j}ai<aj 的点 ii 揷入树状数组中。由于所有的 ii 和 jj 都已事先按照 aa 值排好序, 这样的话只要以双指针的方式在树状数组里揷入点,则对树状数组的揷入操作就能从 O\left(n^{2}\right)O(n2) 次降到 O(n)O(n) 次。
通过这样一个算法流程,我们就用 O(n \log n)O(nlogn) 的时间处理完了关于第二类点对的信息了。此时算法的时间复杂度 是 T(n)=T\left(\left\lfloor\frac{n}{2}\right\rfloor\right)+T\left(\left\lceil\frac{n}{2}\right\rceil\right)+O(n \log n)=O\left(n \log ^{2} n\right)T(n)=T(⌊2n⌋)+T(⌈2n⌉)+O(nlogn)=O(nlog2n)。
【三维偏序(陌上开花)-参考代码-CDQ分治】
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200005;
int n,k,m;
struct node
{
int x,y,z,id,w;
bool operator < (const node &A)const{
if(A.x==x && A.y==y) return z < A.z;
else if(A.x==x) return y < A.y;
return x < A.x;
}
}a[N],b[N],d[N];
int ans[N],c[N],cnt[N];
vector <int> v1,v2[N] ;
int lowbit(int x)
{
return x & (-x);
}
void add(int x,int v)
{
for(int i=x;i<N;i+=lowbit(i)) c[i]+=v;
}
int query(int x)
{
int ans=0;
for(int i=x;i;i-=lowbit(i)) ans+=c[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);
int t1=l,t2=mid+1;
for(int i=l;i<=r;i++)
{
if((t1<=mid && a[t1].y<=a[t2].y) || t2>r)
{
add(a[t1].z,a[t1].w);
b[i]=a[t1++];
}
else
{
cnt[a[t2