前言html
CDQ是誰呢?一位與莫隊,hjt同樣自創算法或數據結構的大佬……node
學習了好幾天,總算對CDQ分治有了一點了解ios
CDQ真的好有用啊,特別是在三維偏序問題上c++
(那些會KD-tree和樹套樹的大佬就不要嘲諷我了……)git
參考文獻:http://www.javashuo.com/article/p-peufsewl-hw.html算法
https://blog.csdn.net/wu_tongtong/article/details/78785836數組
介紹數據結構
CDQ分治是一種很是高級的算法,碼量小,常數小,能夠頂替許多的數據結構,然而缺點是必須離線操做ide
通常來講,CDQ分爲三個步驟:post
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套樹狀數組解決了