zoukankan      html  css  js  c++  java
  • CDQ分治学习笔记

    前言

      CDQ是谁呢?一位与莫队,hjt一样自创算法或数据结构的大佬……

      学习了好几天,总算对CDQ分治有了一点了解

      CDQ真的好有用啊,特别是在三维偏序问题上

      (那些会KD-tree和树套树的大佬就不要嘲讽我了……)

      参考文献:https://www.cnblogs.com/mlystdcall/p/6219421.html

          https://blog.csdn.net/wu_tongtong/article/details/78785836

    介绍

      CDQ分治是一种非常高级的算法,码量小,常数小,可以顶替许多的数据结构,然而缺点是必须离线操作

      一般来说,CDQ分为三个步骤:

      1.分。将原问题划分为若干子问题,子问题间相互独立且与原问题形式相同。一般来说这些问题包含修改和查询两个操作,用区间$[l,r]$表示,将其分为$[l,mid]$和$[mid+1,r]$两个区间

      2.治。递归解决子问题

      3.并。将子问题合并为原问题,并考虑$[l,mid]$区间的操作对$[mid+1,r]$的操作的影响

      然而这么叽里呱啦说了一大堆似乎没有什么用……还是详细的讲一讲好了

    入门:二维偏序

      对于一个点$(x,y)$,若存在点$(x_0,y_0)$满足$x_0>x$且$y_0>y$,则称$(x,y)<(x_0,y_0)$

      二维偏序问题,就是给出好多个点$(x,y)$,求有多少个点大于它

      ps:关于具体是大于还是小于,以及$x$和$y$之间的互相关系,视具体题目而定

      实际上,逆序对就是一个二维偏序问题

      为什么呢?我们可以把数组中的每一个点表示为$(x,y)$,其中$x$表示在数组中的位置,$y$表示权值。求逆序对个数,就是求有多少点对满足$x<x'$且$y>y'$。不就是一个二维偏序问题么?

      逆序对个数我们是怎么求的呢?归并排序

      回忆一下归并排序求逆序对的过程。我们每次合并两个区间的时候,要考虑左子区间对右子区间的影响。即,每次从右子区间中取出一个数时,要把“以这个数结尾的逆序对个数”加上“左子区间内比他小的数的个数”,不就是CDQ分治的过程么

      再来考虑一般的二维偏序,对于每一个点$(x,y)$,我们可以先排序,使得$x$有序,这样,我们就可以只考虑$y$元素了(考虑一下归并排序求逆序对的过程,实际上数组的位置是默认有序的,于是我们分治的时候只需要考虑它的值就可以了)

      如果不用归并的话,也可以用树状数组求逆序对。从左到右考虑每个点,在权值树状数组中加入,每次加入之前在树状数组中查询,看看前面有多少个数比他大就好了

      二维偏序问题的拓展

      给定一个N个元素的序列a,初始值全部为0,对这个序列进行以下两种操作:

      操作1:格式为1 x k,把位置x的元素加上k(位置从1标号到N)。

      操作2:格式为2 x y,求出区间[x,y]内所有元素的和。

      实际上是一道树状数组的裸题(然而好死不死的非要用CDQ分治来做)

      我们把每一个操作看成$(x,y)$,其中$x$表示操作到来的时间,$y$表示操作的点(对于查询操作,我们把它拆成$l-1$和$r$两个区间)。时间这一维是默认有序的,于是我们只要在分治的过程中将$y$这一维从小到大合并就可以了

      那么如何表示修改和查询呢?我们在每一个操作上记录一个类型type,type为1表示修改,type为2表示被拆出来的$l-1$的区间,要对答案有一个负的贡献,type为3表示被拆出来的$r$的区间,对答案有一个正的贡献

      因为实在懒得码了,代码是抄这里

     1 #include <iostream>
     2 #include <cstring>
     3 #include <algorithm>
     4 #include <cstdio>
     5 #include <cstdlib>
     6 #include <cmath>
     7 
     8 using namespace std;
     9 typedef long long ll;
    10 const int MAXN = 500001; // 原数组大小
    11 const int MAXM = 500001; // 操作数量
    12 const int MAXQ = (MAXM<<1)+MAXN;
    13 
    14 int n,m;
    15 
    16 struct Query {
    17     int type, idx; ll val;
    18     bool operator<( const Query &rhs ) const { // 按照位置从小到大排序,修改优先于查询
    19         return idx == rhs.idx ? type < rhs.type : idx < rhs.idx;
    20     }
    21 }query[MAXQ];
    22 int qidx = 0;
    23 
    24 ll ans[MAXQ]; int aidx = 0; // 答案数组
    25 
    26 Query tmp[MAXQ]; // 归并用临时数组
    27 void cdq( int L, int R ) {
    28     if( R-L <= 1 ) return;
    29     int M = (L+R)>>1; cdq(L,M); cdq(M,R);
    30     ll sum = 0;
    31     int p = L, q = M, o = 0;
    32     while( p < M && q < R ) {
    33         if( query[p] < query[q] ) { // 只统计左边区间内的修改值
    34             if( query[p].type == 1 ) sum += query[p].val;
    35             tmp[o++] = query[p++];
    36         }
    37         else { // 只修改右边区间内的查询结果
    38             if( query[q].type == 2 ) ans[query[q].val] -= sum;
    39             else if( query[q].type == 3 ) ans[query[q].val] += sum;
    40             tmp[o++] = query[q++];
    41         }
    42     }
    43     while( p < M ) tmp[o++] = query[p++];
    44     while( q < R ) {
    45         if( query[q].type == 2 ) ans[query[q].val] -= sum;
    46         else if( query[q].type == 3 ) ans[query[q].val] += sum;
    47         tmp[o++] = query[q++];
    48     }
    49     for( int i = 0; i < o; ++i ) query[i+L] = tmp[i];
    50 }
    51 
    52 int main() {
    53     scanf( "%d%d", &n, &m );
    54     for( int i = 1; i <= n; ++i ) { // 把初始元素变为修改操作
    55         query[qidx].idx = i; query[qidx].type = 1;
    56         scanf( "%lld", &query[qidx].val ); ++qidx;
    57     }
    58     for( int i = 0; i < m; ++i ) {
    59         int type; scanf( "%d", &type );
    60         query[qidx].type = type;
    61         if( type == 1 ) scanf( "%d%lld", &query[qidx].idx, &query[qidx].val );
    62         else { // 把查询操作分为两部分
    63             int l,r; scanf( "%d%d", &l, &r );
    64             query[qidx].idx = l-1; query[qidx].val = aidx; ++qidx;
    65             query[qidx].type = 3; query[qidx].idx = r; query[qidx].val = aidx; ++aidx;
    66         }
    67         ++qidx;
    68     }
    69     cdq(0,qidx);
    70     for( int i = 0; i < aidx; ++i ) printf( "%lld
    ", ans[i] );
    71     return 0;
    72 }
    树状数组

    基础:三维偏序

      三维偏序是什么呢?很明显嘛,就是求有多少个点满足$x<=x',y<=y',z<=z'$

      以模板题陌上花开为例(洛谷传送门

      一般的大佬们的做法是树套树,即第一维用排序解决,第二维用权值树状数组,第三维在树状数组的每一个节点套平衡树,然而码量超大且特别难调(才不是因为我不会写才这么说呢)

      那么考虑用CDQ如何解决

      第一维,可以直接用排序解决,排除第一维的影响

      那么考虑第二维和第三维如何使其变得有序呢?

      第二维,我们可以在CDQ分治的过程中使其变得有序,那么我们要做的就是,对于两个区间,其中左区间的$x$都小于右区间的$x$,同一区间内$y$单调递增,求左边$z$小于右边$z$的有几对点

      因为只有一维了,我们可以用上面树状数组的方法解决。从左到右考虑左区间和右区间,并记录两个指针$j$和$k$分别表示左区间和右区间,如果$a[j].y<a[k].y$,则在权值树状数组中加入$a[j].z$,否则就将$a[k]$表示的答案加上在树状数组中查询得到的比$a[k].z$小的数的个数

      为什么这样是对的呢?因为原数组已经按第一维排序过了,所以左区间的$x$必定小于右区间,而因为左右区间分别用归并将$y$排序,所以在用两个指针扫描的时候实际是将这一区间内按$y$排序的过程,可以保证加入的$y$必然是不降的,这样的话,只要查询一下$z$即可

      那么为什么只有左区间加入树状数组,右区间统计答案呢?因为这是归并排序,所以只有左区间对右区间的影响还没有被统计,各自区间内的影响已经被统计过,不需要再考虑了

      顺带一提,每一次统计完之后,记得把树状数组给清空

      时间复杂度$O(nlog^2n)$

      依旧懒得敲代码,还是抄这里

     1 #include<iostream>
     2 #include<iomanip>
     3 #include<cstdio>
     4 #include<cstdlib>
     5 #include<cstring>
     6 #include<cmath>
     7 #include<algorithm>
     8 #define maxn 100010
     9 #define maxk 200010
    10 #define ll long long 
    11 using namespace std;
    12 inline int read()
    13 {
    14     int x=0,f=1;
    15     char ch=getchar();
    16     while(isdigit(ch)==0 && ch!='-')ch=getchar();
    17     if(ch=='-')f=-1,ch=getchar();
    18     while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    19     return x*f;
    20 }
    21 inline void write(int x)
    22 {
    23     int f=0;char ch[20];
    24     if(!x){puts("0");return;}
    25     if(x<0){putchar('-');x=-x;}
    26     while(x)ch[++f]=x%10+'0',x/=10;
    27     while(f)putchar(ch[f--]);
    28     putchar('
    ');
    29 }
    30 typedef struct node
    31 {
    32     int x,y,z,ans,w;    
    33 }stnd;
    34 stnd a[maxn],b[maxn];
    35 int n,cnt[maxk];
    36 int k,n_;
    37 bool cmpx(stnd u,stnd v)
    38 {
    39     if(u.x==v.x)
    40     {
    41         if(u.y==v.y)
    42             return u.z<v.z;
    43         return u.y<v.y;
    44     }
    45     return u.x<v.x;
    46 }
    47 bool cmpy(stnd u,stnd v)
    48 {
    49     if(u.y==v.y)
    50         return u.z<v.z;
    51     return u.y<v.y;
    52 }
    53 struct treearray
    54 {
    55     int tre[maxk],kk;
    56     int lwbt(int x){return x&(-x);}
    57     int ask(int i){int ans=0; for(;i;i-=lwbt(i))ans+=tre[i];return ans;}
    58     void add(int i,int k){for(;i<=kk;i+=lwbt(i))tre[i]+=k;}
    59 }t;
    60 void cdq(int l,int r)
    61 {
    62     if(l==r)return;
    63     int mid=(l+r)>>1;
    64     cdq(l,mid);cdq(mid+1,r);
    65     sort(a+l,a+mid+1,cmpy);
    66     sort(a+mid+1,a+r+1,cmpy);
    67     int i=mid+1,j=l;
    68     for(;i<=r;i++)
    69     {
    70         while(a[j].y<=a[i].y && j<=mid)
    71             t.add(a[j].z,a[j].w),j++;
    72         a[i].ans+=t.ask(a[i].z);
    73     }
    74     for(i=l;i<j;i++)
    75         t.add(a[i].z,-a[i].w);
    76 }
    77 int main()
    78 {
    79     n_=read(),k=read();t.kk=k;
    80     for(int i=1;i<=n_;i++)
    81         b[i].x=read(),b[i].y=read(),b[i].z=read();
    82     sort(b+1,b+n_+1,cmpx);
    83     int c=0;
    84     for(int i=1;i<=n_;i++)
    85     {
    86         c++;
    87         if(b[i].x!=b[i+1].x || b[i].y!=b[i+1].y || b[i].z!=b[i+1].z )
    88             a[++n]=b[i],a[n].w=c,c=0;
    89     } 
    90     cdq(1,n);   
    91     for(int i=1;i<=n;i++)
    92         cnt[a[i].ans+a[i].w-1]+=a[i].w;
    93     for(int i=0;i<n_;i++)
    94         write(cnt[i]);
    95     return 0;
    96 }
    陌上花开(CDQ+树状数组)

      啥?你问本蒟蒻的代码?

      弱弱的说一句,当初我做陌上花开的时候学的是某大佬的CDQ套CDQ

      总而言之,就是先用一层CDQ把$y$值给排的有序了,然后再进一层CDQ把$z$值给排的有序,其实也能求

      然后这代码是我自己敲的了

     1 //minamoto
     2 #include<iostream>
     3 #include<cstdio>
     4 #include<algorithm>
     5 using std::sort;
     6 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
     7 char buf[1<<21],*p1=buf,*p2=buf;
     8 inline int read(){
     9     #define num ch-'0'
    10     char ch;bool flag=0;int res;
    11     while(!isdigit(ch=getc()))
    12     (ch=='-')&&(flag=true);
    13     for(res=num;isdigit(ch=getc());res=res*10+num);
    14     (flag)&&(res=-res);
    15     #undef num
    16     return res;
    17 }
    18 char sr[1<<21],z[20];int C=-1,Z;
    19 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
    20 inline void print(int x){
    21     if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
    22     while(z[++Z]=x%10+48,x/=10);
    23     while(sr[++C]=z[Z],--Z);sr[++C]='
    ';
    24 }
    25 const int N=100005;
    26 int n,k,ans[N],d[N];
    27 struct node{
    28     int x,y,z;
    29     bool b;int *ans;
    30     inline void get(){
    31         x=read(),y=read(),z=read();
    32     }
    33     inline bool operator ==(const node &a)const
    34     {return x==a.x&&y==a.y&&z==a.z;}
    35 }a[N],b[N],c[N];
    36 inline bool cmp(const node &a,const node &b){
    37     return a.x<b.x||(a.x==b.x&&a.y<b.y)||(a.x==b.x&&a.y==b.y&&a.z<b.z);
    38 }
    39 void merge2(int l,int r){
    40     if(l==r) return;
    41     int mid=(l+r)>>1;
    42     merge2(l,mid),merge2(mid+1,r);
    43     int i=l,j=l,k=mid+1,cnt=0;
    44     while(j<=mid&&k<=r){
    45         if(b[j].z<=b[k].z){
    46             c[i]=b[j++],cnt+=c[i].b;
    47         }
    48         else{
    49             c[i]=b[k++];
    50             if(!c[i].b) *c[i].ans+=cnt;
    51         }
    52         ++i;
    53     }
    54     while(j<=mid)
    55     c[i]=b[j++],++i;
    56     while(k<=r){
    57         c[i]=b[k++];
    58         if(!c[i].b) *c[i].ans+=cnt;
    59         ++i;
    60     }
    61     for(int i=l;i<=r;++i) b[i]=c[i];
    62 }
    63 void merge1(int l,int r){
    64     if(l==r) return;
    65     int mid=(l+r)>>1;
    66     merge1(l,mid),merge1(mid+1,r);
    67     int i=l,j=l,k=mid+1;
    68     while(j<=mid&&k<=r){
    69         if(a[j].y<=a[k].y){
    70             b[i]=a[j++],b[i].b=1;
    71         }
    72         else{
    73             b[i]=a[k++],b[i].b=0;
    74         }
    75         ++i;
    76     }
    77     while(j<=mid)
    78     b[i]=a[j++],b[i].b=1,++i;
    79     while(k<=r)
    80     b[i]=a[k++],b[i].b=0,++i;
    81     for(int i=l;i<=r;++i) a[i]=b[i];
    82     merge2(l,r);
    83 }
    84 int main(){
    85     //freopen("testdata.in","r",stdin);
    86     n=read(),k=read();
    87     for(int i=1;i<=n;++i)
    88     a[i].get(),a[i].ans=&ans[i],ans[i]=0;
    89     sort(a+1,a+n+1,cmp);
    90     for(int i=n-1;i;--i)
    91     if(a[i]==a[i+1]) *a[i].ans=*a[i+1].ans+1;
    92     merge1(1,n);
    93     for(int i=1;i<=n;++i) ++d[ans[i]];
    94     for(int i=0;i<n;++i) print(d[i]);
    95     Ot();
    96     return 0;
    97 }
    陌上花开(CDQ套CDQ)

      再提一嘴,理论上来说,CDQ是可以无限套下去的,也就是说不光三维偏序,四维五维六维七维都可以做。然而时间复杂度是$O(nlog^kn)$的($k$是维数),高维的时候还不如打个暴力算了

      三维偏序问题的拓展

      以园丁的烦恼为例

      平面上有N个点,每个点的横纵坐标在$[0,1e^7]$之间,有$M$个询问,每个询问为查询在指定矩形之内有多少个点,矩形用$(x1,y1,x2,y2)$的方式给出,其中$(x1,y1)$为左下角坐标,$(x2,y2)$为右上角坐标。

      不用CDQ分治的话直接权值树状数组就可以了

      然而CDQ应该怎么做呢?

      我们联想到上面,把每一个点的位置变成修改,同时进行差分,把每一个询问拆成四个前缀和查询,然后不难发现每一个操作都有时间,$x$和$y$三个维度,直接用CDQ解决三维偏序问题就可以了

      依然是抄这里的代码

      1 #include <iostream>
      2 #include <cstring>
      3 #include <algorithm>
      4 #include <cstdio>
      5 #include <cmath>
      6 #include <cstdlib>
      7 #include <cctype>
      8 
      9 using namespace std;
     10 const int MAXN = 500001; // 点的数量
     11 const int MAXM = 500001; // 询问数量
     12 const int MAXQ = MAXN+(MAXM<<2);
     13 const int MAXL = 10000002; // 树状数组大小
     14 
     15 int n, m, maxy = -1;
     16 
     17 namespace IO { // 快读相关
     18     const int BUFSZ = 1e7;
     19     char buf[BUFSZ]; int idx, end;
     20     void init() { idx = BUFSZ; }
     21     char getch() {
     22         if( idx == BUFSZ ) {
     23             end = fread( buf, 1, BUFSZ, stdin ); idx = 0;
     24         }
     25         if( idx == end ) return EOF;
     26         return buf[idx++];
     27     }
     28     int getint() {
     29         int num = 0; char ch;
     30         while( isspace(ch=getch()) );
     31         do { num = num*10 + ch-'0'; } while( isdigit(ch=getch()) );
     32         return num;
     33     }
     34 }
     35 using IO::getint;
     36 
     37 struct Query {
     38     int type, x, y, w, aid; // w表示对查询结果贡献(+还是-),aid是“第几个查询”
     39     bool operator<( const Query &rhs ) const {
     40         return x == rhs.x ? type < rhs.type : x < rhs.x;
     41     }
     42 }query[MAXQ];
     43 int qidx = 0;
     44 void addq( int type, int x, int y, int w, int aid ) {
     45     query[qidx++] = (Query){type,x,y,w,aid};
     46 }
     47 
     48 int ans[MAXM], aidx = 0;
     49 
     50 namespace BIT { // 树状数组相关
     51     int arr[MAXL];
     52     inline int lowbit( int num ) { return num&(-num); }
     53     void add( int idx, int val ) {
     54         while( idx <= maxy ) {
     55             arr[idx] += val;
     56             idx += lowbit(idx);
     57         }
     58     }
     59     int query( int idx ) {
     60         int ans = 0;
     61         while( idx ) {
     62             ans += arr[idx];
     63             idx -= lowbit(idx);
     64         }
     65         return ans;
     66     }
     67     void clear( int idx ){
     68         while( idx <= maxy ) {
     69             if( arr[idx] ) arr[idx] = 0; else break;
     70             idx += lowbit(idx);
     71         }
     72     }
     73 }
     74 
     75 Query tmp[MAXQ];
     76 void cdq( int L, int R ) {
     77     if( R-L <= 1 ) return;
     78     int M = (L+R)>>1; cdq(L,M); cdq(M,R);
     79     int p = L, q = M, o = L;
     80     while( p < M && q < R ) {
     81         if( query[p] < query[q] ) {
     82             if( query[p].type == 0 ) BIT::add( query[p].y, 1 );
     83             tmp[o++] = query[p++];
     84         } else {
     85             if( query[q].type == 1 ) ans[query[q].aid] += query[q].w * BIT::query( query[q].y );
     86             tmp[o++] = query[q++];
     87         }
     88     }
     89     while( p < M ) tmp[o++] = query[p++];
     90     while( q < R ) {
     91         if( query[q].type == 1 ) ans[query[q].aid] += query[q].w * BIT::query( query[q].y );
     92         tmp[o++] = query[q++];
     93     }
     94     for( int i = L; i < R; ++i ) {
     95         BIT::clear( tmp[i].y ); // 清空树状数组
     96         query[i] = tmp[i];
     97     }
     98 }
     99 
    100 int main() {
    101     IO::init(); n = getint(); m = getint();
    102     while( n-- ) {
    103         int x,y; x = getint(); y = getint(); ++x; ++y; // 为了方便,把坐标转化为[1,1e7+1]
    104         addq(0,x,y,0,0); maxy = max( maxy, y ); // 修改操作无附加信息
    105     }
    106     while( m-- ) {
    107         int x1,y1,x2,y2; x1 = getint(); y1 = getint(); x2 = getint(); y2 = getint(); ++x1; ++y1; ++x2; ++y2;
    108         addq(1,x1-1,y1-1,1,aidx); addq(1,x1-1,y2,-1,aidx); addq(1,x2,y1-1,-1,aidx); addq(1,x2,y2,1,aidx); ++aidx;
    109         maxy = max( maxy, max(y1,y2) );
    110     }
    111     cdq(0,qidx);
    112     for( int i = 0; i < aidx; ++i ) printf( "%d
    ", ans[i] );
    113     return 0;
    114 }
    园丁的烦恼(CDQ)

      至于我的代码?额……我当初是直接把$y$给离散,然后排序$x$,$y$用权值树状数组解决的(我也很想知道当初为什么会这么做……)

     1 //minamoto
     2 #include<iostream>
     3 #include<cstdio>
     4 #include<algorithm>
     5 using std::sort;
     6 using std::unique;
     7 using std::lower_bound;
     8 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
     9 char buf[1<<21],*p1=buf,*p2=buf;
    10 inline int read(){
    11     #define num ch-'0'
    12     char ch;bool flag=0;int res;
    13     while(!isdigit(ch=getc()))
    14     (ch=='-')&&(flag=true);
    15     for(res=num;isdigit(ch=getc());res=res*10+num);
    16     (flag)&&(res=-res);
    17     #undef num
    18     return res;
    19 }
    20 char sr[1<<21],z[20];int C=-1,Z;
    21 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
    22 inline void print(int x){
    23     if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
    24     while(z[++Z]=x%10+48,x/=10);
    25     while(sr[++C]=z[Z],--Z);sr[++C]='
    ';
    26 }
    27 const int N=500005;
    28 int x[N],y[N],a[N],b[N],c[N],d[N],p[N*5];
    29 int n,m,tot,cnt;
    30 struct node{
    31     int x,y,id,type;
    32     inline void add(int a,int b,int c=0,int d=0)
    33     {x=a,y=b,id=c,type=d;}
    34     inline bool operator <(const node &b)const
    35     {return x<b.x||(x==b.x&&type<b.type);}
    36 }q[N*5];
    37 int res[N],ans[N][5];
    38 inline void add(int x,int val){
    39     for(int i=x;i<=n;i+=i&(-i))
    40     res[i]+=val;
    41 }
    42 inline int query(int x){
    43     int ans=0;
    44     for(int i=x;i;i-=i&(-i))
    45     ans+=res[i];
    46     return ans;
    47 }
    48 int main(){
    49     //freopen("testdata.in","r",stdin);
    50     n=read(),m=read();
    51     for(int i=1;i<=n;++i){
    52         x[i]=read()+1,y[i]=read()+1;
    53         p[++tot]=y[i];
    54     }
    55     for(int i=1;i<=m;++i){
    56         a[i]=read()+1,b[i]=read()+1,c[i]=read()+1,d[i]=read()+1;
    57         p[++tot]=b[i],p[++tot]=d[i];
    58     }
    59     sort(p+1,p+1+tot);
    60     tot=unique(p+1,p+1+tot)-p-1;
    61     for(int i=1;i<=n;++i){
    62         y[i]=lower_bound(p+1,p+1+tot,y[i])-p;
    63         q[++cnt].add(x[i],y[i]);
    64     }
    65     for(int i=1;i<=m;++i){
    66         b[i]=lower_bound(p+1,p+1+tot,b[i])-p;
    67         d[i]=lower_bound(p+1,p+1+tot,d[i])-p;
    68         q[++cnt].add(a[i]-1,b[i]-1,i,1),q[++cnt].add(a[i]-1,d[i],i,2);
    69         q[++cnt].add(c[i],b[i]-1,i,3),q[++cnt].add(c[i],d[i],i,4);
    70     }
    71     sort(q+1,q+1+cnt);
    72     for(int i=1;i<=cnt;++i){
    73         if(!q[i].type) add(q[i].y,1);
    74         else ans[q[i].id][q[i].type]=query(q[i].y);
    75     }
    76     for(int i=1;i<=m;++i){
    77         int k=ans[i][4]-ans[i][3]-ans[i][2]+ans[i][1];
    78         print(k);
    79     }
    80     Ot();
    81     return 0;
    82 }
    园丁的烦恼(树状数组)

    题目

      然而说了这么多也没啥用……直接来几道题目讲一讲好了

      ps:传送门都是洛谷的

    COGS1752. [BOI2007]摩基亚Mokia

      和上面园丁的烦恼其实差不多。询问直接拆成四个前缀和询问,然后每一个操作都有时间,$x$和$y$这三个维度,为了保证有序,直接CDQ+树状数组带走

      题解->这里

    BZOJ 3110 [Zjoi2013]K大数查询 

      据说是一道CDQ的板子(zi第三声)

      然鹅为什么越看越像整体二分……

      虽然完全不知道这两个东西有什么区别

      大概唯一的区别就是不只是询问,连答案都得一起归并找吧……

      题解->这里

    bzoj 2244 [SDOI2011]拦截导弹

      CDQ用处还真大……还能用来优化dp……

      如果做过初级的导弹拦截应该知道这一类题目都是dp

      然而这是一个三维的LIS……所以只好上CDQ啦

      树状数组用来维护之前的LIS最大值,然后不断转移就行了

      题解->这里

    【bzoj 2716】[Violet 3]天使玩偶 

      和上面一样,树状数组用于维护之前的最大值

      然后这题目还要转……很麻烦……

      不过可以用来练习CDQ优化dp

      题解->这里

    洛谷P1393 动态逆序对

      我记得以前写的是树状数组套主席树的写法(当刚知道这题还有CDQ的解法时很震惊)

      (这次为什么没有抄代码呢,因为洛谷上有两道动态逆序对,所以我才敲了两种解法)

      我们把删除看成倒着加入,于是就在普通的逆序对上多加了时间的一维,直接带进去搞就行了

      题解->这里

    SACO17FEB]Why Did the Cow Cross the Road III P(CDQ分治)

      这道题最重要的是转化模型

      只要看出三维偏序的模型就可以直接CDQ爆搞了

      题解->这里

    总结

      CDQ的所有题目,只要能将模型转化为多维偏序(一般是三维偏序,四维一般只能CDQ套CDQ套树状数组了),就可以用CDQ套树状数组解决了

  • 相关阅读:
    Redhat 8 nfs网络共享
    Cisco ASA NAT (1) 静态配置和端口映射
    DISM命令行工具修复Windows 10映像
    開啟windows 7 ,10 的熱點功能(無線熱點)
    symantec SMG 抓包
    在 MacBook 上安装 Ubuntu
    springboot整合的MongoDB(一){博主新人入坑,不对的地方还望大哥指出}
    feign接口无法传递参数 (初学者)
    low cache rba恢复到on disk rba
    控制文件被删除的测试
  • 原文地址:https://www.cnblogs.com/bztMinamoto/p/9463508.html
Copyright © 2011-2022 走看看