CDQ分治學習筆記

前言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

  總而言之,就是先用一層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套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\n", 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]='\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:傳送門都是洛谷的

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套樹狀數組解決了

相關文章
相關標籤/搜索