CDQ 总结

tip:
  CDQ 分治主要处理三维偏序问题,解题时主要是找出比较量(三个或两个),并找出适当排序顺序(有时不需))。  

  CDQ 分治常常与树状数组搭配,树状数组主要用来统计前缀和(权值前缀和 / 排名)、最值、逆序对。

实战:

T1:陌上花开

题干:

  有 n 朵花,每朵花有三个属性:花形 (s)、颜色 (c)、气味 (m),用三个整数表示。现在要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量。

  定义一朵花 A 比另一朵花 B 要美丽,当且仅当 Sa>=Sb , Ca>=Cb , Ma>=Mb 。

  显然,两朵花可能有同样的属性。需要统计出每个等级的花的数量。

题解:
  比较明显就可以看出这是一道 CDQ 分治,三个比较量就是 S、C、M,排序顺序对这道题来说没有影响。

  需要注意的是,我们可以发现有可能两朵花的属性完全一样,根据题意来看,这两朵花互相更美丽。。。

  注意一下我们需要求出的是每个等级的花的数量,而不是每朵花的等级。

  在统计答案时需要去重,将完全一样的花合成一朵,只是权值增加了;

  当然,也可以不去重,只是我们需要找出 完全相同的花中答案最大的 作为这种花的等级。

  我们可以将 S sort 一下,将 C 用 CDQ 分治解决,将 M 压进树状数组中来统计。

  在统计答案时(通法),我们直接在结构体中建立一个 ans 变量。因为本题的结果与 ans 更新的顺序没有直接关系,能更新就更新即可

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cstdlib>
 4 #include<algorithm>
 5 #define $ 200010
 6 using namespace std;
 7 int m,n,k,t,ans[$*2],cnt,tr[$*2];
 8 struct tree{
 9     int s,c,m,t,sum,ans;  
10     friend bool operator != (tree x,tree y){
11         if(x.s!=y.s) return 1;
12         if(x.c!=y.c) return 1;
13         if(x.m!=y.m) return 1;
14         return 0;
15     }  
16     friend bool operator <  (tree x,tree y){
17         if(x.s!=y.s) return x.s<y.s;
18         if(x.c!=y.c) return x.c<y.c;
19         return x.m<y.m;
20     }
21 }a[$],pre[$],now[$];
22 inline void add(int x,int add){
23     if(x==0) return;
24     for(register int i=x;i<=k;i+=i&(-i)) tr[i]+=add; 
25 }
26 inline int ask(int x,int ans=0){
27     for(register int i=x;i>=1;i-=i&(-i)) ans+=tr[i];
28     return ans;
29 }
30 inline void CDQ(int l,int r){
31     if(l==r) return;
32     int mid=(l+r)>>1;
33     CDQ(l,mid);  CDQ(mid+1,r);
34     int left=l,right=mid+1,tip=0;
35     while(left<=mid&&right<=r){
36         if(a[left].c<=a[right].c)
37             add(a[left].m,a[left].sum), now[++tip]=a[left++];
38         else
39             a[right].ans+=ask(a[right].m), now[++tip]=a[right++];
40     }
41     while(right<=r)
42         a[right].ans+=ask(a[right].m), now[++tip]=a[right++];
43     for(register int i=l;i<left;++i) add(a[i].m,0);
44     while(left<=mid)  now[++tip]=a[left++];
45     for(register int i=l;i<=r;++i)   a[i]=now[i-l+1];
46 }
47 signed main(){
48     scanf("%d%d",&n,&k);
49     for(register int i=1;i<=n;++i)
50         scanf("%d%d%d",&pre[i].s,&pre[i].c,&pre[i].m);
51     sort(pre+1,pre+n+1);
52     for(register int i=1,sum=1;i<=n;++i,++sum)
53         if(pre[i+1]!=pre[i]) a[++cnt]=pre[i], a[cnt].sum=sum, sum=0;
54     CDQ(1,cnt);
55     for(register int i=1;i<=cnt;++i) ans[a[i].ans+a[i].sum]+=a[i].sum;
56     for(register int i=1;i<=n;++i)   printf("%d
",ans[i]);
57 }
有合并
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cstdlib>
 4 #include<algorithm>
 5 #define $ 200010
 6 using namespace std;
 7 int m,n,k,t,ans[$*2],cnt,tr[$*2];
 8 struct tree{
 9     int s,c,m,t,ans;  
10     friend bool operator == (tree x,tree y){
11         if(x.s!=y.s) return 0;
12         if(x.c!=y.c) return 0;
13         if(x.m!=y.m) return 0;
14         return 1;
15     }  
16     friend bool operator <  (tree x,tree y){
17         if(x.s!=y.s) return x.s<y.s;
18         if(x.c!=y.c) return x.c<y.c;
19         if(x.m!=y.m) return x.m<y.m;
20         return x.ans<y.ans;
21     }
22 }a[$],pre[$],now[$];
23 inline void add(int x,int add){
24     if(x==0) return;
25     for(register int i=x;i<=k;i+=i&(-i)) tr[i]+=add; 
26 }
27 inline int ask(int x,int ans=0){
28     for(register int i=x;i>=1;i-=i&(-i)) ans+=tr[i];
29     return ans;
30 }
31 inline void CDQ(int l,int r){
32     if(l==r) return;
33     int mid=(l+r)>>1;
34     CDQ(l,mid);  CDQ(mid+1,r);
35     int left=l,right=mid+1,tip=0;
36     while(left<=mid&&right<=r){
37         if(a[left].c<=a[right].c)
38             add(a[left].m,1), now[++tip]=a[left++];
39         else
40             a[right].ans+=ask(a[right].m), now[++tip]=a[right++];
41     }
42     while(right<=r)
43         a[right].ans+=ask(a[right].m), now[++tip]=a[right++];
44     for(register int i=l;i<left;++i) add(a[i].m,0);
45     while(left<=mid)  now[++tip]=a[left++];
46     for(register int i=l;i<=r;++i)   a[i]=now[i-l+1];
47 }
48 signed main(){
49     scanf("%d%d",&n,&k);
50     for(register int i=1;i<=n;++i)
51         scanf("%d%d%d",&a[i].s,&a[i].c,&a[i].m);
52     sort(a+1,a+n+1);  CDQ(1,cnt);  sort(a+1,a+n+1);
53     int tip=a[cnt].ans;
54     for(register int i=cnt;i>=1;--i){
55         if(a[i]==a[i-1]) ans[tip]++; 
56         else tip=
57     }
58     for(register int i=1;i<=n;++i)   printf("%d
",ans[i]);
59 }
无合并

 

T2:Mokia / 简单题

题干:

  维护一个 W * W 的矩阵,初始值均为 S .每次操作可以增加某格子的权值,或询问某子矩阵的总权值.修改操作数 M<=160000,询问数 Q<=10000 , W<=2000000.
  第一行两个整数 , S , W ;其中 S 为矩阵初始值; W 为矩阵大小
  接下来每行为一下三种输入之一(不包含引号):
  "1 x y a"
  "2 x1 y1 x2 y2"
  "3"
  输入 1 :你需要把 (x,y) (第 x 行第 y 列)的格子权值增加 a
  输入 2 :你需要求出以左下角为 (x1,y1) ,右上角为 (x2,y2) 的矩阵内所有格子的权值和,并输出
  输入 3 :表示输入结束

题解:

  看到 w=2000000 ,数组模拟不优秀,空间、时间复杂度都不行。

  我们可以发现,每种操作都与时间有关,结果的更新也与时间有关;同时操作又与坐标有关。发现这几个变量,我们就可以想到用 CDQ 分治。

  但是‘2’操作有 4 个坐标,用哪两个呢?其实我们不难看出只有最外面的那个坐标起决定作用,因为修改操作只有在访问区域内才可能做贡献

  我们可以利用单步容斥的方法,将一个查询操作分成 4 个查询操作,时间戳一样,还是按照原来的排序顺序(t > x > y)排序即可。

  我们发现, $ans$ 的结果与更新它的时间没有关系,我们完全可以将其作为一个二维偏序问题来解决。

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cstdlib>
 4 #include<algorithm>
 5 #define $ 800010
 6 using namespace std;
 7 int m,n,k,t,s,w,cnt,ans[$],tr[$*3],x,y,xx,yy,derta,opt,num;
 8 struct tree{
 9     int opt,x,y,val,t;
10     friend bool operator < (tree x,tree y){
11         if(x.x!=y.x) return x.x<y.x;
12         if(x.y!=y.y) return x.y<y.y;
13         return x.opt<y.opt;
14     }
15 }a[$],now[$];
16 inline void add(int x,int add){
17     if(x==0) return;
18     if(add==0){
19         for(register int i=x;i<=w;i+=i&(-i)) tr[i]=0;
20         return;
21     }
22     for(register int i=x;i<=w;i+=i&(-i)) tr[i]+=add;
23 }
24 inline int ask(int x,int ans=0){
25     for(register int i=x;i>=1;i-=i&(-i)) ans+=tr[i];
26     return ans;
27 }
28 inline void CDQ(int l,int r){
29     if(l==r) return ;
30     int mid=(l+r)>>1,tip=0,left=l,right=mid+1;
31     CDQ(l,mid);  CDQ(mid+1,r);
32     while(left<=mid&&right<=r){
33         if(a[left].x<=a[right].x){
34             if(a[left].opt==0) add(a[left].y,a[left].val); 
35             now[++tip]=a[left]; ++left; 
36         }
37         else{
38             if(a[right].opt==1) 
39                 ans[a[right].t]+=ask(a[right].y)*a[right].val;
40             now[++tip]=a[right]; ++right;
41         }
42     }
43     while(left<=mid) now[++tip]=a[left], ++left;
44     while(right<=r){
45         if(a[right].opt) ans[a[right].t]+=ask(a[right].y)*a[right].val;
46         now[++tip]=a[right]; ++right;
47     }
48     for(register int i=l;i<=mid;++i) add(a[i].y,0);
49     for(register int i=l;i<=r;++i) a[i]=now[i-l+1];
50 }
51 signed main(){    
52     scanf("%d%d",&s,&w);
53     while(~scanf("%d",&opt)){
54         if(opt==1){
55             scanf("%d%d%d",&x,&y,&derta);
56             a[++cnt]=(tree){    0,x,y,derta,0    };
57         }
58         if(opt==2){
59             scanf("%d%d%d%d",&x,&y,&xx,&yy); ++num;
60             ans[num]=(yy-y+1)*(xx-x+1)*s;
61             a[++cnt]=(tree){    1,xx,yy,1,num    };
62             a[++cnt]=(tree){    1,x-1,y-1,1,num    };
63             a[++cnt]=(tree){    1,x-1,yy,-1,num    };
64             a[++cnt]=(tree){    1,xx,y-1,-1,num    };
65         }
66         if(opt==3) break;
67     }
68     CDQ(1,cnt);
69     for(register int i=1;i<=num;++i)  printf("%d
",ans[i]);
70 }
排序顺序为 x > y > opt
 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #define $ 2200000
 6 #define int long long
 7 using namespace std;
 8 struct tree {    int t,x,y,opt,f,val;    }a[$],now[$];
 9 int s,w,tot,opt,up,tr[$*8];
10 inline bool cmp(const tree &a,const tree &b){
11     if(a.t!=b.t)  return a.t<b.t;
12     if(a.x!=b.x)  return a.x<b.x;
13     return a.y<b.y;
14 }
15 inline void add(int x,int add){
16     if(add==0){
17         for(register int i=x;i<=w;i+=i&(-i))  tr[i]=0;
18         return;
19     }
20     for(register int i=x;i<=w;i+=i&(-i))  tr[i]+=add;
21 }
22 inline int query(int x,int ans=0){
23     for(register int i=x;i>=1;i-=i&(-i))  ans+=tr[i];
24     return ans;
25 }
26 inline void CDQ(int l,int r){
27     if(l==r)return;
28     int mid=(l+r)>>1,left=l,right=mid+1,tip=-1;
29     CDQ(l,mid),CDQ(mid+1,r);
30     while(left<=mid&&right<=r){
31         if(a[left].x<=a[right].x){
32             if(!a[left].opt) add(a[left].y,a[left].val);
33             now[++tip]=a[left++];
34         }else{
35             if(a[right].opt) a[right].f+=query(a[right].y);
36             now[++tip]=a[right++];
37         }
38     }
39     while(right<=r){
40         if(a[right].opt) a[right].f+=query(a[right].y);
41         now[++tip]=a[right++];
42     }
43     for(register int i=l;i<left;++i) add(a[i].y,0);
44     while(left<=mid)  now[++tip]=a[left++];
45     for(register int i=l;i<=r;++i)   a[i]=now[i-l];
46 }
47 signed main(){
48     scanf("%lld%lld",&s,&w); ++w;
49     for(register int i=1,x,y,z,xx,yy;;++i){
50         scanf("%lld",&opt);
51         if(opt==1){
52             scanf("%lld%lld%lld",&x,&y,&z);
53             a[++tot]=(tree){i,x+1,y+1,0,0,z};
54         }
55         if(opt==2){
56             scanf("%lld%lld%lld%lld",&x,&y,&xx,&yy);
57             a[++tot]=(tree){    i,xx+1,yy+1,1,0,0    };
58             a[++tot]=(tree){    i,x,yy+1,-1,0,0    };
59             a[++tot]=(tree){    i,xx+1,y,-1,0,0    };
60             a[++tot]=(tree){    i,x,y,1,0,0    };
61         }
62         if(opt==3) break;
63     }
64     sort(a+1,a+1+tot,cmp);  CDQ(1,tot);
65     sort(a+1,a+1+tot,cmp);
66     for(register int i=1;i<=tot;i+=4){
67         if(!a[i].opt) continue;
68         int ans=0;
69         for(register int j=1;j<=4;++j)
70             ans+=a[i+j-1].opt*(a[i+j-1].f+a[i+j-1].x*a[i+j-1].y*s);
71         printf("%lld
",ans);
72     }
73 }
排序顺序为 t > x > y

T3:天使玩偶

题干:

题解:

  注意曼哈顿距离是有绝对值的,我们不可以简单地将 $x+y$ 压入树状数组来比较,只有这个点在目标节点的左下方才可以进行比较

  我们就可以进行 4 次 CDQ,每次都变换一下每个点的坐标,就相当于是转动了坐标系,分 4 次将答案更新完成。

  我们又可以发现,更新答案与时间无相互限制,可以不保证 t 的单调。

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cstdlib>
 4 #include<algorithm>
 5 #define $ 1001000
 6 #define inf 0x3f3f3f3
 7 #define getchar() (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)
 8 using namespace std;
 9 int m,n,k,t,cnt,num,ans[$],tr[$*2],maxx;
10 struct tree{    int opt,x,y;    }a[$],now[$],pre[$];
11 char xB[(1<<15)+10],*xS=xB,*xT=xB;
12 inline int read(int sum=0){
13     register char ch=getchar();
14     for(sum=0;ch<'0'||ch>'9';ch=getchar());
15     for(;ch>='0'&&ch<='9';sum=(sum<<1)+(sum<<3)+ch-'0',ch=getchar());
16     return sum;
17 }
18 inline int min(int x,int y){    return x<y?x:y;    }
19 inline int max(int x,int y){    return x>y?x:y;    }
20 inline void add(int x,int add){
21     if(x==0) return;
22     if(add==-inf){
23         for(register int i=x;i<=maxx;i+=i&(-i)) tr[i]=0;
24         return;
25     }
26     for(register int i=x;i<=maxx;i+=i&(-i)) tr[i]=max(tr[i],add);
27 }
28 inline int ask(int x,int ans=0){
29     for(register int i=x;i>=1;i-=i&(-i)) ans=max(ans,tr[i]);
30     return (ans==0)?-inf:ans;
31 } 
32 inline void CDQ(int l,int r){
33     if(l==r) return;
34     int mid=(l+r)>>1,left=l,right=mid+1,tip=0;
35     CDQ(l,mid);   CDQ(mid+1,r);
36     while(left<=mid&&right<=r){
37         if(a[left].x<=a[right].x){
38             if(!a[left].opt)  add(a[left].y,a[left].x+a[left].y);
39             now[++tip]=a[left]; ++left;
40         }
41         else{
42             if(a[right].opt) 
43                 ans[a[right].opt]=min(ans[a[right].opt],a[right].x+a[right].y-ask(a[right].y));
44             now[++tip]=a[right]; ++right;
45         }
46     }
47     while(right<=r){
48         if(a[right].opt)     
49             ans[a[right].opt]=min(ans[a[right].opt],a[right].x+a[right].y-ask(a[right].y));
50         now[++tip]=a[right], ++right;
51     }
52     for(register int i=l;i<left;++i) add(a[i].y,-inf);
53     while(left<=mid) now[++tip]=a[left], ++left;
54     for(register int i=l;i<=r;++i)   a[i]=now[i-l+1];
55 }
56 signed main(){
57     memset(ans,63,sizeof(ans));
58     scanf("%d%d",&n,&m);
59     for(register int i=1,x,y;i<=n;++i){
60         x=read(), y=read();
61         pre[++cnt]=(tree){    0,x+1,y+1    };
62         maxx=max(maxx,x+y);
63     }
64     for(register int i=1,x,y,opt;i<=m;++i){
65         opt=read(), x=read(), y=read();
66         if(opt==2) opt=++num;
67         else opt=0;
68         pre[++cnt]=(tree){    opt,x+1,y+1    };
69         maxx=max(maxx,x+y);
70     } 
71     maxx+=100;
72     for(register int i=1;i<=cnt;++i) a[i]=(tree){    pre[i].opt,pre[i].x,pre[i].y    };
73     CDQ(1,cnt);
74     for(register int i=1;i<=cnt;++i) a[i]=(tree){    pre[i].opt,maxx-pre[i].x,pre[i].y    };
75     CDQ(1,cnt); 
76     for(register int i=1;i<=cnt;++i) a[i]=(tree){    pre[i].opt,pre[i].x,maxx-pre[i].y    };
77     CDQ(1,cnt); 
78     for(register int i=1;i<=cnt;++i) a[i]=(tree){    pre[i].opt,maxx-pre[i].x,maxx-pre[i].y    };
79     CDQ(1,cnt); 
80     for(register int i=1;i<=num;++i) printf("%d
",ans[i]);
81 }
x > y > opt

T4:动态逆序对

  对于序列 A ,它的逆序对数定义为满足 i A j 的数对 (i,j) 的个数。给 1 到 n 的一个排列,按照某种顺序依次删除 m 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

  ‘按照顺序删除一个数’可以转化为‘按照顺序添加一个数’;相应地,‘删除元素之前统计’就是‘添加元素之后统计’,这就符合我们平时做题时的按序添加的 CDQ 分治问题。

  发现时间与答案统计有关,我们就还是三维排序即可。

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define $ 600100
 5 #define int long long
 6 using namespace std;
 7 int m,n,k,t,cnt,mrk[$],ans[$/2],maxx,tr[$*10];
 8 struct tree{
 9     int t,id,w,ans;
10     friend bool operator < (tree x,tree y){
11         if(x.t!=y.t)   return x.t<y.t;
12         if(x.id!=y.id) return x.id<y.id;
13         return x.w<y.w;
14     }
15 }a[$],now[$],pre[$];
16 inline int max(int x,int y){    return x>y?x:y;    }
17 inline void add(int x,int add){
18     if(x==0) return ;
19     for(register int i=x;i<=maxx;i+=i&(-i)) tr[i]+=add;
20 }
21 inline int ask(int x,int ans=0){
22     for(register int i=x;i>=1;i-=i&(-i)) ans+=tr[i];
23     return ans;
24 }
25 inline void CDQ(int l,int r){
26     if(l==r) return;
27     int mid=(l+r)>>1,left=l,right=mid+1,tip=-1;
28     CDQ(l,mid);  CDQ(mid+1,r);
29     while(left<=mid&&right<=r){
30         if(a[left].id<a[right].id)
31             add(a[left].w,1), now[++tip]=a[left++];
32         else
33             ans[a[right].t]+=ask(a[right].w), now[++tip]=a[right++];
34     }
35     while(right<=r)
36         ans[a[right].t]+=ask(a[right].w), now[++tip]=a[right++];
37     for(register int i=l;i<left;++i) add(a[i].w,-1);
38     while(left<=mid) now[++tip]=a[left++];
39     for(register int i=l;i<=r;++i) a[i]=now[i-l];
40 }
41 signed main(){
42     scanf("%lld%lld",&n,&m);  cnt=m;
43     for(register int i=1;i<=n;++i)
44         scanf("%lld",&a[i].w), a[i].id=i, maxx=max(maxx,a[i].w);
45     for(register int i=1,x;i<=m;++i) scanf("%lld",&x), mrk[x]=i;
46     for(register int i=1;i<=n;++i) if(mrk[i]==0) mrk[i]=++cnt;
47     for(register int i=1;i<=n;++i) a[i].t=n-mrk[a[i].w]+1;
48     for(register int i=1;i<=n;++i) a[i].w=maxx-a[i].w+1;
49     sort(a+1,a+n+1); CDQ(1,n);
50     for(register int i=1;i<=n;++i) a[i].w=maxx-a[i].w+1, a[i].id=n-a[i].id+1;
51     sort(a+1,a+n+1); CDQ(1,n);
52     for(register int i=1;i<=n;++i) ans[i]+=ans[i-1];
53     for(register int i=n;i>=n-m+1;--i) printf("%lld
",ans[i]);
54 }
t > x > y
越努力 越幸运
原文地址:https://www.cnblogs.com/OI-zzyy/p/11253707.html