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\n", 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('\n'); 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把$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]='\n'; 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是可以无限套下去的,也就是说不光三维偏序,四维五维六维七维都可以做。然而时间复杂度是$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\n", ans[i] ); 113 return 0; 114 }
至于我的代码?额……我当初是直接把$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]='\n'; 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:传送门都是洛谷的
和上面园丁的烦恼其实差不多。询问直接拆成四个前缀和询问,然后每一个操作都有时间,$x$和$y$这三个维度,为了保证有序,直接CDQ+树状数组带走
题解->这里
据说是一道CDQ的板子(zi第三声)
然鹅为什么越看越像整体二分……
虽然完全不知道这两个东西有什么区别
大概唯一的区别就是不只是询问,连答案都得一起归并找吧……
题解->这里
CDQ用处还真大……还能用来优化dp……
如果做过初级的导弹拦截应该知道这一类题目都是dp
然而这是一个三维的LIS……所以只好上CDQ啦
树状数组用来维护之前的LIS最大值,然后不断转移就行了
题解->这里
和上面一样,树状数组用于维护之前的最大值
然后这题目还要转……很麻烦……
不过可以用来练习CDQ优化dp
题解->这里
我记得以前写的是树状数组套主席树的写法(当刚知道这题还有CDQ的解法时很震惊)
(这次为什么没有抄代码呢,因为洛谷上有两道动态逆序对,所以我才敲了两种解法)
我们把删除看成倒着加入,于是就在普通的逆序对上多加了时间的一维,直接带进去搞就行了
题解->这里
SACO17FEB]Why Did the Cow Cross the Road III P(CDQ分治)
这道题最重要的是转化模型
只要看出三维偏序的模型就可以直接CDQ爆搞了
题解->这里
总结
CDQ的所有题目,只要能将模型转化为多维偏序(一般是三维偏序,四维一般只能CDQ套CDQ套树状数组了),就可以用CDQ套树状数组解决了