笔记-CDQ 分治
CDQ 分治
分治,分而治之,一般采取递归的形式,先将要处理的部分分开分别处理,再合并计算。
而 CDQ 分治正是基于分治思想的离线算法。
具体地,CDQ 分治对询问进行分治,对于一个询问区间
- 处理
。 - 处理
。 - 计算
中的修改对 的贡献。
CDQ 分治常用于解决点对相关问题。
对于偏序问题,CDQ 分治可以在较优的时间复杂度内求出,如逆序对等二维偏序。归并排序就是 CDQ 分治思想的一种体现。
接下来进入一道例题。
有
个元素,第 个元素有 三个属性,设 表示满足 且 且 且 的 的数量。 对于
,求 的数量。
, 。
经典题。
显然这道题是不能
首先将点按
分治。对于区间
将两个区间中的点按 sort
),然后双指针,
对于一个 query(c[i])
即可。
注意清空树状数组。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=2e5+5;
struct node{
int a,b,c;
int ans,cnt;
}a[N],A[N];
int n,m,len;
int ans[N];
namespace BIT{
int tree[M];
void add(int x,int k){
for(;x<=m;x+=x&-x)
tree[x]+=k;
}
int query(int x){
int res=0;
for(;x;x-=x&-x)
res+=tree[x];
return res;
}
}
using BIT::add;
using BIT::query;
bool cmpa(node a,node b){
if(a.a!=b.a)return a.a<b.a;
if(a.b!=b.b)return a.b<b.b;
return a.c<b.c;
}
bool cmpb(node a,node b){
if(a.b!=b.b)return a.b<b.b;
return a.c<b.c;
}
void cdq(int l,int r){
if(l>=r)return;
int mid=l+r>>1;
cdq(l,mid);
cdq(mid+1,r);
sort(a+l,a+mid+1,cmpb);//按 b 排序
sort(a+mid+1,a+r+1,cmpb);
int i,j;
for(i=mid+1,j=l;i<=r;i++){
while(j<=mid&&a[j].b<=a[i].b){
add(a[j].c,a[j].cnt);//树状数组维护 c
j++;
}
a[i].ans+=query(a[i].c);
}
for(i=l;i<j;i++)
add(a[i].c,-a[i].cnt);
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>A[i].a>>A[i].b>>A[i].c;
sort(A+1,A+1+n,cmpa);//按 a 排序,保证分开的区间前后 a 相对有序
int tot=1;
for(int i=2;i<=n;i++){//将点去重简化计算
if(A[i].a!=A[i-1].a||A[i].b!=A[i-1].b||A[i].c!=A[i-1].c){
a[++len]={A[i-1].a,A[i-1].b,A[i-1].c,0,tot};
tot=1;
}else{
tot++;
}
}
a[++len]={A[n].a,A[n].b,A[n].c,0,tot};
cdq(1,len);
for(int i=1;i<=len;i++)
ans[a[i].ans+a[i].cnt-1]+=a[i].cnt;
for(int i=0;i<n;i++)
cout<<ans[i]<<'\n';
return 0;
}
单点加,区间查。
其实 CDQ 分治也能维护这个东西。
把对
二维偏序果断想到 CDQ。我们按时间顺序保存每个操作,对于区间
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
struct node{
int op,pos,val,id;//op=1修改,=2查询[1,r],=3查询[1,l-1]
//pos:操作的位置,val:修改的值/第val个答案
//id:时间顺序
bool operator <(const node &n)const{
if(pos!=n.pos)return pos<n.pos;
return id<n.id;
}
}q[N*3],tmp[N*3];//tmp:归并数组
int n,m,cnt;
int p,ans[N];
void cdq(int l,int r){
if(l==r)return;
int mid=l+r>>1;
cdq(l,mid);
cdq(mid+1,r);
int i,j,k,sum=0;
for(i=l,j=mid+1,k=l;i<=mid&&j<=r;){//累加贡献
if(q[i]<q[j]){
if(q[i].op==1)sum+=q[i].val;
tmp[k++]=q[i++];
}else{//将贡献累加到答案数组
if(q[j].op==2)ans[q[j].val]-=sum;
if(q[j].op==3)ans[q[j].val]+=sum;
tmp[k++]=q[j++];
}
}
for(;i<=mid;){
if(q[i].op==1)sum+=q[i].val;
tmp[k++]=q[i++];
}
for(;j<=r;){
if(q[j].op==2)ans[q[j].val]-=sum;
if(q[j].op==3)ans[q[j].val]+=sum;
tmp[k++]=q[j++];
}
for(i=l;i<=r;i++)q[i]=tmp[i];
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cnt++;
q[cnt].op=1;
q[cnt].pos=i;
cin>>q[cnt].val;
}
for(int i=1;i<=m;i++){
int op;
cin>>op;
if(op==1){
cnt++;
q[cnt].op=1;
q[cnt].id=i;
cin>>q[cnt].pos>>q[cnt].val;
}else{//拆分询问
p++;
cnt++;
q[cnt].op=2;
q[cnt].val=p;
q[cnt].id=i;
cin>>q[cnt].pos;
q[cnt].pos--;
cnt++;
q[cnt].op=3;
q[cnt].val=p;
q[cnt].id=i;
cin>>q[cnt].pos;
}
}
cdq(1,cnt);
for(int i=1;i<=p;i++)
cout<<ans[i]<<'\n';
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!