[11/07/19] CDQ学习笔记
CDQ算法据说是对付离线操作下修改查询问题的重要工具,是基于分治算法的降维操作。
CDQ算法核心思想只有三步:递归处理左区间,处理左区间对右区间的影响,递归处理右区间。原理的话按照我这个蒟蒻的理解就是在完成任意查询操作前完成此查询操作前的修改操作,并且因为是基于分治算法,一定严格按照顺序完成查询操作,从而保证前面的操作不受后面操作的影响,保证了正确性。优化的地方则在于可以优雅的嵌套以维护多维的大小关系,比如保证多元组 满足 等等,还可以用来优化 。
适用范围 一般是要离线并且有明确线性操作顺序并且操作有修改和查询并且能抽象出多元组模型。(有些问题是求区间值,一般也作为多元组中一个参数)例如查询满足 包含/有明确上下限 的问题
操作过程 (以三元组为例)
我们得到了一堆三元组 与一个任务: 对于每个 ,求出使 且 且 的 的个数 ,然后求出对于每个 ,有几个 与之相等。
先按关键字 排序.
然后分治,把当前区间 分成 与 ,分别按 为关键字排序.(如代码,可以直接归并排!)记录此数来自右区间或左区间。
不要有什么乱七八糟的想法。比如说你认为如果这样排序 就乱掉了,你是对的,但我们只考虑左区间与右区间的大小关系,很明显右区间的每一个 都大于左区间的 。又很明显分治到一定程度以后,除了最前面也就是 最小的那个 ,其他每个 都可能到当前子问题的右区间。原理就是在以 排完序后只有当前 前的数可能对当前的 答案有贡献。
然后如果问题有很多很多维,重复上述过程直到剩下最后一维。
然后对于最后一维本来应该套个树状数组或者其他数据结构,但是其实可以直接再套一层CDQ求答案。其实这层CDQ也是以归并排序的形式 循环的,但并不需要归并,所以代码里会有废话(其实也不是废话,但是只要把 转移即可)。归并时若当前选中的多元组来自在每一维均来自左区间,指针 ,若均来自右区间,则当前 的答案加上 。
才不是我打不来树状数组,注意去重,如果有重复多元组合并,当前 的答案可以直接 。
由于会有一大堆的排序,我们必须把 定为 类型以保证地址不变,也就是干脆直接关联 与 。
#include<bits/stdc++.h>
using namespace std;
struct note{
int x,y,z;
bool flg;
int *ans;
}a[101010],b[101010],c[101010];
int n,k,t[101010],ans[101010];
inline bool cmp(note a,note b){ //千万不要用三目运算符,除非你会
if(a.x!=b.x) return a.x<b.x;
if(a.y!=b.y) return a.y<b.y;
return a.z<b.z;
}
inline int read(){
int ret=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
inline void st2(int l,int r){
if(l==r)return;
int mid=l+r>>1;
st2(l,mid);st2(mid+1,r);
int j=l,k=mid+1,cnt=0;
for(int i=l;i<=r;i++){
if((k>r||b[j].z<=b[k].z)&&j<=mid){ //z为关键字归并
c[i]=b[j++]; //废话
cnt+=c[i].flg; //只要 cnt+=b[j].flg,j++;即可
}
else{
c[i]=b[k++]; //废话
if(!c[i].flg)*c[i].ans+=cnt; //只要 if(!b[k].flg)*b[k].ans+=cnt;k++;即可
}
}
for(int i=l;i<=r;i++)b[i]=c[i];
}
inline void st1(int l,int r){
if(l==r)return;
int mid=l+r>>1;
st1(l,mid);st1(mid+1,r);
int j=l,k=mid+1;
for(int i=l;i<=r;i++){
if((k>r||a[j].y<=a[k].y)&&j<=mid)b[i]=a[j++],b[i].flg=1; //y为关键字归并入左区间的数
else b[i]=a[k++],b[i].flg=0; //归并入右区间的数
}
for(int i=l;i<=r;i++)a[i]=b[i];
st2(l,r);
}
int main(){
n=read(),k=read();
for(int i=1;i<=n;i++)a[i].x=read(),a[i].y=read(),a[i].z=read(); //读入
for(int i=1;i<=n;i++)a[i].ans=&ans[i],ans[i]=0;sort(a+1,a+n+1,cmp); //统一地址及排序
for(int i=n-1;i>=1;i--)if(a[i].x==a[i+1].x&&a[i].y==a[i+1].y&&a[i].z==a[i+1].z)*a[i].ans=*a[i+1].ans+1; //去重
st1(1,n); //分治
for(int i=1;i<=n;i++)t[ans[i]]++; //桶排
for(int i=0;i<n;i++)printf("%d\n",t[i]);
return 0;
}
不过像 Mokia 之类有修改操作且修改数据非常数的最好还是用数据结构维护....也不是说CDQ不行,只是改起来烦。
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int ch=getchar(),res=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
while(isdigit(ch))res=(res<<3)+(res<<1)+(ch^48),ch=getchar();
return res*f;
}
const int N=500005;
struct ask{int l,r,a,b;}q1[N],a[N];
int s,n,tot,cnt,ans[N],tr[2000000];
inline int lowbit(int x){return (x&(-x));}
inline void update(int a,int k){for(;a<=n;a+=lowbit(a))tr[a]+=k;}
inline int query(int a,int res=0){for(;a;a-=lowbit(a))res+=tr[a];return res;}
inline bool cmp(ask a,ask b){if(a.l!=b.l)return a.l<b.l;return a.r<b.r;}
inline 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,cmp);
sort(a+mid+1,a+r+1,cmp);
int i=l;
for(int j=mid+1;j<=r;j++){
for(;i<=mid&&a[i].l<=a[j].l;i++)
if(a[i].b==1)update(a[i].r,a[i].a);
if(a[j].b==2)ans[a[j].a]+=query(a[j].r);
}
for(int j=l;j<i;j++)if(a[j].b==1)update(a[j].r,-a[j].a);
}
int main(){
s=read(),n=read();
int b=read();
while(b!=3){
if(b==1){
a[++tot].l=read(),a[tot].r=read(),a[tot].a=read(),a[tot].b=1;
}
else{
int x1=read(),y1=read(),x2=read(),y2=read();
a[++tot].l=x2,a[tot].r=y2,a[tot].a=++cnt,a[tot].b=2;
a[++tot].l=x1-1,a[tot].r=y2,a[tot].a=++cnt,a[tot].b=2;
a[++tot].l=x2,a[tot].r=y1-1,a[tot].a=++cnt,a[tot].b=2;
a[++tot].l=x1-1,a[tot].r=y1-1,a[tot].a=++cnt,a[tot].b=2;
}
b=read();
}
cdq(1,tot);
for(int i=1;i<=cnt;i+=4){
cout<<(ans[i]-ans[i+1]-ans[i+2]+ans[i+3])<<'\n';
}
}
附四维代码
//四维陌上花开
#include<bits/stdc++.h>
using namespace std;
struct note{
int x,y,z,cz;
bool flg1,flg2;
int *ans;
}a[101010],b[101010],c[101010],d[101010];
int m,n,k,t[101010],ans[101010];
int X1,X2,Y1,Y2,Z1,Z2,tot;
inline bool cmp(note a,note b){ //千万不要用三目运算符,除非你会
if(a.x!=b.x) return a.x<b.x;
if(a.y!=b.y) return a.y<b.y;
if(a.z!=b.z) return a.z<b.z;
return a.cz<b.cz;
}
inline int read(){
int ret=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
inline void st3(int l,int r){
if(l==r)return;
int mid=l+r>>1;
st3(l,mid);st3(mid+1,r);
int j=l,k=mid+1,cnt=0;
for(int i=l;i<=r;i++){
if((k>r||c[j].cz<=c[k].cz)&&j<=mid){
d[i]=c[j++];
cnt+=d[i].flg1&d[i].flg2; //如果每一次修改操作不一样的话可以写成cnt+=(d[i].flg1&d[i].flg2)*d[i].xiugaizhi;
//如果不止一维改成cnt+=d[i].flg1&d[i].flg2&...&d[i].flgn;即可
}
else{
d[i]=c[k++];
if(!d[i].flg1&&!d[i].flg2) *d[i].ans+=cnt; //每一维都来自右区间
}
}
for(int i=l;i<=r;i++)c[i]=d[i];
}
inline void st2(int l,int r){
if(l==r)return;
int mid=l+r>>1;
st2(l,mid);st2(mid+1,r);
int j=l,k=mid+1;
for(int i=l;i<=r;i++){
if((k>r||b[j].z<=b[k].z)&&j<=mid)c[i]=b[j++],c[i].flg2=1;
else c[i]=b[k++],c[i].flg2=0;
}
for(int i=l;i<=r;i++)b[i]=c[i];
st3(l,r);
}
inline void st1(int l,int r){
if(l==r)return;
int mid=l+r>>1;
st1(l,mid);st1(mid+1,r);
int j=l,k=mid+1;
for(int i=l;i<=r;i++){
if((k>r||a[j].y<=a[k].y)&&j<=mid)b[i]=a[j++],b[i].flg1=1;
else b[i]=a[k++],b[i].flg1=0;
}
for(int i=l;i<=r;i++)a[i]=b[i];
st2(l,r);
}
int main(){
n=read();
for(int i=1;i<=n;i++) a[i].x=read(),a[i].y=read(),a[i].z=read(),a[i].cz=read(); //读入
for(int i=1;i<=n;i++) a[i].ans=&ans[i],ans[i]=0;sort(a+1,a+n+1,cmp); //统一地址及排序
st1(1,n);for(register int i=1;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}
//HDU5126
#pragma GCC optimize(2)
#include<bits/stdc++.h>//STO MOD LargestJN Orz %%%%%%
using namespace std;
struct note{
int x,y,z,cz;
bool flg1,flg2;
int *ans;
}a[101010],b[101010],c[101010],d[101010];
int m,n,k,t[101010],ans[101010];
int X1,X2,Y1,Y2,Z1,Z2,tot;
inline bool cmp(note a,note b){
if(a.x!=b.x) return a.x<b.x;
if(a.y!=b.y) return a.y<b.y;
return a.z<b.z;
}
inline int read(){
int ret=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
inline void st3(int l,int r){
if(l==r)return;
int mid=l+r>>1;
st3(l,mid);st3(mid+1,r);
int j=l,k=mid+1,cnt=0;
for(int i=l;i<=r;i++){
if((k>r||c[j].z<=c[k].z)&&j<=mid){
d[i]=c[j++];
if(d[i].flg1&&d[i].flg2&&d[i].cz)cnt++;
}
else{
d[i]=c[k++];
if(!d[i].flg1&&!d[i].flg2&&!d[i].cz)*d[i].ans+=cnt;
}
}
for(int i=l;i<=r;i++)c[i]=d[i];
}
inline void st2(int l,int r){
if(l==r)return;
int mid=l+r>>1;
st2(l,mid);st2(mid+1,r);
int j=l,k=mid+1;
for(int i=l;i<=r;i++){
if((k>r||b[j].y<=b[k].y)&&j<=mid)c[i]=b[j++],c[i].flg2=1;
else c[i]=b[k++],c[i].flg2=0;
}
for(int i=l;i<=r;i++)b[i]=c[i];
st3(l,r);
}
inline void st1(int l,int r){
if(l==r)return;
int mid=l+r>>1;
st1(l,mid);st1(mid+1,r);
int j=l,k=mid+1;
for(int i=l;i<=r;i++){
if((k>r||a[j].x<=a[k].x)&&j<=mid)b[i]=a[j++],b[i].flg1=1;
else b[i]=a[k++],b[i].flg1=0;
}
for(int i=l;i<=r;i++)a[i]=b[i];
st2(l,r);
}
int main(){
n=read();
for(int i=1;i<=n;i++){
k=read();
if(k==1)a[++m].x=read()+1,a[m].y=read()+1,a[m].z=read()+1,a[m].cz=1;
else {
X1=read(),Y1=read(),Z1=read();
X2=read(),Y2=read(),Z2=read();
X2++,Y2++,Z2++;t[++tot]=m+1;
a[++m].x=X2,a[m].y=Y2,a[m].z=Z2;
a[m].ans=&ans[m];ans[m]=0;
a[++m].x=X1,a[m].y=Y2,a[m].z=Z2;
a[m].ans=&ans[m];ans[m]=0;
a[++m].x=X2,a[m].y=Y1,a[m].z=Z2;
a[m].ans=&ans[m];ans[m]=0;
a[++m].x=X2,a[m].y=Y2,a[m].z=Z1;
a[m].ans=&ans[m];ans[m]=0;
a[++m].x=X1,a[m].y=Y1,a[m].z=Z2;
a[m].ans=&ans[m];ans[m]=0;
a[++m].x=X1,a[m].y=Y2,a[m].z=Z1;
a[m].ans=&ans[m];ans[m]=0;
a[++m].x=X2,a[m].y=Y1,a[m].z=Z1;
a[m].ans=&ans[m];ans[m]=0;
a[++m].x=X1,a[m].y=Y1,a[m].z=Z1;
a[m].ans=&ans[m];ans[m]=0;
}
}
st1(1,m);
for(int i=1;i<=tot;i++) printf("%d\n",ans[t[i]]-ans[t[i]+1]-ans[t[i]+2]-ans[t[i]+3]+ans[t[i]+4]+ans[t[i]+5]+ans[t[i]+6]-ans[t[i]+7]);//千万千万注意,不要把t[i]+n写成t[i+n]!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具