CDQ 分治
CDQ 分治是一种离线分治算法。
前言/闲话
在考虑三维偏序进行CDQ分治时每一维用什么处理时,一般会把较难的用sort和树状数组解决,剩下的一维再用归并排序;
其实可以乱试,也就
写博客时:
啊啊啊,写的好烦啊,实在不想看或赶时间,就看代码+画图+感性理解+细致分析讨论算了...
重点是要找出题目性质可以使用
CDQ分治。CDQ分治很多种变式的,不要硬套模板,应该随机应变。
引入介绍
CDQ分治是用于解决多维偏序的问题,基于分治思想,可以潜意识理解为归并排序优化,一般上常见的有二维偏序和三维偏序。
怎么理解偏序呢,我的理解是:做一些题就知道了。
二维偏序
我们很早时候接触的逆序对个数问题其实就是二维偏序,满足一组
树状数组求逆序对比较常用,这里展示的是归并排序算法(有助于理解三维偏序):
在归并的时候,对于区间
1.左区间
内部的贡献,这里可以直接向下递归; 2.同理,对于右区间
内部的贡献,也可以直接向下递归; 3.重点:左区间
和右区间 交叉形成的贡献。
由于左区间
(注:处理这个部分既可以归并时sort,也可以一边计算贡献一边排序,区别是第一种方法简单易懂,用途广,但时间复杂度会多一个log级;另一种呢,速度较上一种快,但归并时思考量大一些,且有些特殊的三位偏序无法解决。)
且这里左右贡献都已经计算了,所以我们只用关心右区间内的数原本的下标
两维条件中
接着考虑满足 顺带 计算贡献,这里的左区间和右区间的
当
当
最后还需考虑多出来的
时间复杂度是
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,a[N],tem[N],ans;
inline void MergeSort(int l,int r) {
if(l==r)
return ;
int mid=(l+r)>>1;
MergeSort(l,mid);
MergeSort(mid+1,r);
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r) {
if(a[i]<=a[j])
tem[++k]=a[i++];
else {
tem[++k]=a[j++];
ans+=mid-i+1;
}
}
while(i<=mid) tem[++k]=a[i++];
while(j<=r) tem[++k]=a[j++];
for(int i=l,j=1;i<=r;i++)
a[i]=tem[j++];
return ;
}
signed main() {
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
MergeSort(1,n);
printf("%lld\n",ans);
return 0;
}CF1311F Moving Points
Solution
分类讨论题,画个坐标轴,考虑两个维数:位置大小
先默认
同向移动时,若
异向移动时,若
整理一下,这就是个二维偏序:
- ①
- ②
套板子就可以了,注意第一维用sort处理掉。
三维偏序
【模板】三维偏序(陌上花开)
Solution
很明显给出了三维:
对于一组
按上面说的,先用sort消掉第一维
这时候考虑如何解决第三维,尝试用
小坑点:注意要去重(也不是完全意义上的去重),为什么呢?
可能有三维都完全相同的元素,本来这些元素相互之间都有贡献,可是在CDQ分治的过程中只有左区间贡献右区间;
就是说对于
1 2 3,2 5 6,2 5 6,7 8 9
在归并时,第二个元素会对第三个元素产生
为了解决这个问题,我们需要在一开始就把序列去重,并用 CDQ分治在归并时,向树状数组中
普遍三维偏序的CDQ分治就是归并排序中加了个树状数组,一般为时间复杂度可以看作
总结
小结一下做CDQ分治的思考步骤:
-- 待更新
【模板】Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
const int M=2e5+10;
int n,m,k;
struct Node {
int x,y,z;
int s,cnt;
} a[N],tem[N];
int sum[M],ans[N];
inline bool cmp(Node x,Node y) {
if(x.x!=y.x)
return x.x<y.x;
if(x.y!=y.y)
return x.y<y.y;
return x.z<y.z;
}
inline int lowbit(int x) {
return x&(-x);
}
inline void add(int x,int v) {
while(x<=k) {
sum[x]+=v;
x+=lowbit(x);
}
return ;
}
inline int query(int x) {
int Sum=0;
while(x) {
Sum+=sum[x];
x-=lowbit(x);
}
return Sum;
}
inline void cdq(int ls,int rs) {
if(ls>=rs)
return ;
int mid=(ls+rs)>>1;
cdq(ls,mid);
cdq(mid+1,rs);
int cur=0,i=ls,j=mid+1;
while(i<=mid&&j<=rs) {
if(a[i].y<=a[j].y) {
add(a[i].z,a[i].s);
tem[++cur]=a[i++];
} else {
a[j].cnt+=query(a[j].z);
tem[++cur]=a[j++];
}
}
while(i<=mid) {
add(a[i].z,a[i].s);
tem[++cur]=a[i++];
}
while(j<=rs) {
a[j].cnt+=query(a[j].z);
tem[++cur]=a[j++];
}
for(int i=ls;i<=mid;i++)
add(a[i].z,-a[i].s);
for(int i=ls,j=1;j<=cur;i++,j++)
a[i]=tem[j];
return ;
}
inline bool equal(Node x,Node y) {
if(x.x==y.x&&x.y==y.y&&x.z==y.z)
return true;
return false;
}
signed main() {
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++) {
scanf("%lld%lld%lld",&a[i].x,&a[i].y,&a[i].z);
a[i].s=1;
}
sort(a+1,a+n+1,cmp);
m=1;
for(int i=2;i<=n;i++) {
if(equal(a[m],a[i])) {
a[m].s++;
continue;
}
a[++m]=a[i];
}
cdq(1,m);
for(int i=1;i<=m;i++)
ans[a[i].cnt+a[i].s-1]+=a[i].s;
for(int i=0;i<n;i++)
printf("%lld\n",ans[i]);
return 0;
}四维偏序
不想讲了,就是多套一维,CDQ套CDQ套树状数组...