$cdq$分治主要思想就是将操作离线下来,然后分治之后统计二分之后,左边的修改对于右边查询的贡献。可以顶替很多复杂的数据结构。
学会下面这几类问题差不多就会$cdq$了。
个人认为看代码比较容易学。
二维偏序
先考虑这样一类问题
给出n个二元组$(a,b)$,求出有多少对$i,j$满足$a_i<b_i$并且$a_j<b_j$。
一个显然的思路就是,先按照$a_i$排个序,然后树状数组求下顺序对。
那么,用$CDQ$分治应该怎么做呢。
同样先按照$a_i$排个序,然后用归并排序求顺序对。
归并排序就是最简单的$CDQ$分治。
这种问题叫做二维偏序,可以发现,我们用$sort$完成了一维的排序,又用$CDQ$解决了另一维的排序。
然后我们考虑一下归并排序的具体思想。
先二分一下,递归两边,使两边都排好序。这时可以保证的是,右面部分的第一维一定比左边部分的第一维大(不一定是有序的)。两边内部的贡献我们都已经统计好了。现在只要统计出左边对于右边的贡献就行了。这些贡献都是可以$O(1)$计算的
1 /* 2 * @Author: wxyww 3 * @Date: 2019-02-14 08:32:37 4 * @Last Modified time: 2019-02-15 07:45:40 5 */ 6 #include<cstdio> 7 #include<iostream> 8 #include<cstdlib> 9 #include<cstring> 10 #include<algorithm> 11 #include<queue> 12 #include<vector> 13 #include<ctime> 14 using namespace std; 15 typedef long long ll; 16 #define int ll 17 const int N = 2000000 + 10; 18 ll read() { 19 ll x=0,f=1;char c=getchar(); 20 while(c<'0'||c>'9') { 21 if(c=='-') f=-1; 22 c=getchar(); 23 } 24 while(c>='0'&&c<='9') { 25 x=x*10+c-'0'; 26 c=getchar(); 27 } 28 return x*f; 29 } 30 struct node { 31 int id,val,opt; 32 }a[N],tmp[N]; 33 int ans[N]; 34 void cdq(int l,int r) { 35 if(r - l < 1) return; 36 int mid = (l + r) >> 1; 37 cdq(l,mid); 38 cdq(mid + 1,r); 39 int L = l,R = mid + 1; 40 int sum = 0; 41 int now = l; 42 while(L <= mid && R <= r) { 43 if(a[L].id < a[R].id || (a[L].opt < a[R].opt && a[L].id == a[R].id)) { 44 if(a[L].opt == 1) sum += a[L].val; 45 tmp[now++] = a[L++]; 46 } 47 else { 48 if(a[R].opt == 2) ans[a[R].val] -= sum; 49 else if(a[R].opt == 3) ans[a[R].val] += sum; 50 tmp[now++] = a[R++]; 51 } 52 } 53 while(R <= r) { 54 if(a[R].opt == 2) ans[a[R].val] -= sum; 55 else if(a[R].opt == 3)ans[a[R].val] += sum; 56 tmp[now++] = a[R++]; 57 } 58 while(L <= mid) tmp[now++] = a[L++]; 59 for(int i = l;i <= r;++i) a[i] = tmp[i]; 60 } 61 signed main() { 62 int tot = 0; 63 int n = read(),Q = read(); 64 for(int i = 1;i <= n;++i) { 65 a[++tot].id = i;a[tot].opt = 1; 66 a[tot].val = read(); 67 } 68 int now = 0; 69 for(int i = 1;i <= Q;++i) { 70 int opt = read(); 71 if(opt == 1) { 72 a[++tot].opt = 1; 73 a[tot].id = read();a[tot].val = read(); 74 } 75 else { 76 a[++tot].opt = 2;a[tot].id = read() - 1;a[tot].val = ++now; 77 a[++tot].opt = 3;a[tot].id = read();a[tot].val = now; 78 } 79 } 80 81 cdq(1,tot); 82 for(int i = 1;i <= now;++i) printf("%lld\n",ans[i]); 83 return 0; 84 }
三维偏序
给出$n$个三元组$(a,b,c)$,求有多少对$i,j$满足$a_i\le a_j,b_i\le b_j,c_i\le c_j$
和上面同样的方法,先按照$a_i$排个序,然后$CDQ$分治一下第二维。那第三维怎么办呢。
树状数组!!!
第三维在$CDQ$的过程中用个树状数组维护一下。
具体就是先递归处理两边,然后两边都变成了第二维度有序的。
然后在双指针扫的过程中,对于右面的元素查询树状数组中小于等于当前第三维的数字的数量,统计答案。
1 /* 2 * @Author: wxyww 3 * @Date: 2019-01-15 18:28:46 4 * @Last Modified time: 2019-02-15 07:46:57 5 */ 6 #include<cstdio> 7 #include<iostream> 8 #include<cstdlib> 9 #include<cmath> 10 #include<ctime> 11 #include<bitset> 12 #include<cstring> 13 #include<algorithm> 14 #include<string> 15 #include<queue> 16 #include<vector> 17 using namespace std; 18 typedef long long ll; 19 const int N = 300000 + 100; 20 ll read() { 21 ll x=0,f=1;char c=getchar(); 22 while(c<'0'||c>'9') { 23 if(c=='-') f=-1; 24 c=getchar(); 25 } 26 while(c>='0'&&c<='9') { 27 x=x*10+c-'0'; 28 c=getchar(); 29 } 30 return x*f; 31 } 32 int tree[N << 1]; 33 int n,K; 34 struct node { 35 int a,b,c,id; 36 }e[N]; 37 bool cmp(node x,node y) { 38 if(x.a != y.a) return x.a < y.a; 39 if(x.b != y.b) return x.b < y.b; 40 return x.c < y.c; 41 } 42 node tmp[N]; 43 int ans[N]; 44 void update(int pos,int c) { 45 while(pos <= K) { 46 tree[pos] += c; 47 pos += pos & -pos; 48 } 49 } 50 int query(int pos) { 51 int re = 0; 52 while(pos >= 1) { 53 re += tree[pos]; 54 pos -= pos & -pos; 55 } 56 return re; 57 } 58 int f[N]; 59 int siz[N]; 60 void work(int l,int r) { 61 if(l == r) return; 62 int mid = (l + r) >> 1; 63 work(l,mid); 64 work(mid + 1,r); 65 int L = l,R = mid + 1; 66 int js = l; 67 while(L <= mid && R <= r) { 68 if(e[R].b >= e[L].b) { 69 update(e[L].c,siz[e[L].id]); 70 tmp[js++] = e[L++]; 71 } 72 else { 73 f[e[R].id] += query(e[R].c); 74 tmp[js++] = e[R++]; 75 } 76 } 77 while (R <= r)f[e[R].id] += query(e[R].c), tmp[js++] = e[R++]; 78 for (int x = l; x < L; x++)update(e[x].c, -siz[e[x].id]); 79 while (L <= mid)tmp[js++] = e[L++]; 80 for (int i = l; i <= r; i++)e[i] = tmp[i]; 81 } 82 int main() { 83 n = read(),K = read(); 84 for(int i =1 ;i <= n;++i) { 85 e[i].a = read();e[i].b = read();e[i].c = read(); 86 } 87 int nn = n; 88 sort(e + 1,e + n + 1,cmp); 89 int js = 0; 90 for(int i = 1;i <= n;++i) { 91 if(e[i].a != e[i - 1].a || e[i].b != e[i - 1].b || e[i].c != e[i - 1].c) tmp[++js] = e[i]; 92 siz[js]++; 93 } 94 for(int i = 1;i <= js;++i) e[i] = tmp[i],e[i].id = i; 95 n = js; 96 work(1,n); 97 for(int i = 1;i <= n;++i) ans[f[e[i].id] + siz[e[i].id] - 1] += siz[e[i].id]; 98 for(int i = 0;i < nn;++i) printf("%d\n",ans[i]); 99 return 0; 100 } 101 // 10 3 3 3 3 2 3 3 2 3 1 3 1 1 3 1 2 1 3 1 1 1 2 1 2 2 1 3 2 1 2 1 102 103 /* 104 5 3 105 3 3 3 106 2 3 3 107 2 3 1 108 3 1 1 109 3 1 2 110 1 3 1 111 1 1 2 112 1 2 2 113 1 3 2 114 1 2 1 115 */
矩阵前缀和
需要支持两种操作,每个操作可能是在某个位置加上某个值,也可能是查询某个子矩阵中数字之和。
利用矩阵前缀和将每个子矩阵查询变为前缀矩阵的查询。然后用$cdq$分治
对于每个查询,记录下查询的时间,横坐标,纵坐标。
对于每个修改,记录下修改的时间,横坐标,纵坐标,修改的值。
这样就变成了一个三维偏序的问题。
第一维查询时间是默认有序的。
第二维横坐标用$cdq$来搞。
第三位纵坐标用树状数组。
每次分治之后对于右边询问的答案统计其实就是查询左边的修改操作中横坐标小于等于当前值,且纵坐标小于等于当前值的修改之和。
/* * @Author: wxyww * @Date: 2019-02-14 14:09:58 * @Last Modified time: 2019-02-14 15:20:54 */ #include<cstdio> #include<iostream> #include<cstdlib> #include<cstring> #include<algorithm> #include<queue> #include<vector> #include<ctime> using namespace std; typedef long long ll; const int N = 300000 + 10; ll read() { ll x=0,f=1;char c=getchar(); while(c<'0'||c>'9') { if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') { x=x*10+c-'0'; c=getchar(); } return x*f; } struct node { int x,y,id,opt,val; }a[N],tmp[N]; int ans[N],tree[N * 100],S,W; void update(int pos,int c) { while(pos <= W + 2) { tree[pos] += c; pos += pos & -pos; } } int query(int pos) { int ret = 0; while(pos >= 1) { ret += tree[pos]; pos -= pos & -pos; } return ret; } vector<pair<int,int> >tt; void cdq(int l,int r) { if(r <= l) return; int mid = (l + r) >> 1; cdq(l,mid);cdq(mid + 1,r); tt.clear(); int L = l,R = mid + 1,now = l; while(L <= mid && R <= r) { if(a[L].x <= a[R].x) { if(a[L].opt == 1) update(a[L].y,a[L].val),tt.push_back(make_pair(a[L].y,a[L].val)); tmp[now++] = a[L++]; } else { if(a[R].opt == 2) ans[a[R].id] += query(a[R].y); else if(a[R].opt == 3) ans[a[R].id] -= query(a[R].y); tmp[now++] = a[R++]; } } while(L <= mid) tmp[now++] = a[L++]; while(R <= r) { if(a[R].opt == 2) ans[a[R].id] += query(a[R].y); else if(a[R].opt == 3) ans[a[R].id] -= query(a[R].y); tmp[now++] = a[R++]; } for(int i = l;i <= r;++i) a[i] = tmp[i]; int k = tt.size(); for(int i = 0;i < k;++i) update(tt[i].first,-tt[i].second); } int main() { S = read(),W = read(); int tot = 0; int now = 0; while(1) { int opt = read();if(opt == 3)break; if(opt == 1) { a[++tot].x = read() + 2;a[tot].y = read() + 2;a[tot].val = read(); a[tot].opt = 1; } else { int x1 = read() + 2,y1 = read() + 2,x2 = read() + 2,y2 = read() + 2; a[++tot].id = ++now;a[tot].x = x1 - 1,a[tot].y = y1 - 1;a[tot].opt = 2; a[++tot].id = now;a[tot].x = x2,a[tot].y = y2;a[tot].opt = 2; a[++tot].id = now;a[tot].x = x1 - 1,a[tot].y = y2,a[tot].opt = 3; a[++tot].id = now;a[tot].x = x2,a[tot].y = y1 - 1,a[tot].opt = 3; ans[now] += (x2 - x1 + 1) * (y2 - y1 + 1) * S; } } cdq(1,tot); for(int i = 1;i <= now;++i) printf("%d\n",ans[i]); return 0; }
1 /* 2 * @Author: wxyww 3 * @Date: 2019-02-14 14:09:58 4 * @Last Modified time: 2019-02-14 15:19:44 5 */ 6 #include<cstdio> 7 #include<iostream> 8 #include<cstdlib> 9 #include<cstring> 10 #include<algorithm> 11 #include<queue> 12 #include<vector> 13 #include<ctime> 14 using namespace std; 15 typedef long long ll; 16 const int N = 1000000 + 10; 17 ll read() { 18 ll x=0,f=1;char c=getchar(); 19 while(c<'0'||c>'9') { 20 if(c=='-') f=-1; 21 c=getchar(); 22 } 23 while(c>='0'&&c<='9') { 24 x=x*10+c-'0'; 25 c=getchar(); 26 } 27 return x*f; 28 } 29 struct node { 30 int x,y,id,opt,val; 31 }a[N],tmp[N]; 32 int ans[N],tree[N],S,W; 33 void update(int pos,int c) { 34 while(pos <= W + 2) { 35 tree[pos] += c; 36 pos += pos & -pos; 37 } 38 } 39 int query(int pos) { 40 int ret = 0; 41 while(pos >= 1) { 42 ret += tree[pos]; 43 pos -= pos & -pos; 44 } 45 return ret; 46 } 47 vector<pair<int,int> >tt; 48 void cdq(int l,int r) { 49 if(r <= l) return; 50 int mid = (l + r) >> 1; 51 cdq(l,mid);cdq(mid + 1,r); 52 tt.clear(); 53 int L = l,R = mid + 1,now = l; 54 while(L <= mid && R <= r) { 55 if(a[L].x <= a[R].x) { 56 if(a[L].opt == 1) update(a[L].y,a[L].val),tt.push_back(make_pair(a[L].y,a[L].val)); 57 tmp[now++] = a[L++]; 58 } 59 else { 60 if(a[R].opt == 2) ans[a[R].id] += query(a[R].y); 61 else if(a[R].opt == 3) ans[a[R].id] -= query(a[R].y); 62 tmp[now++] = a[R++]; 63 } 64 } 65 while(L <= mid) tmp[now++] = a[L++]; 66 while(R <= r) { 67 if(a[R].opt == 2) ans[a[R].id] += query(a[R].y); 68 else if(a[R].opt == 3) ans[a[R].id] -= query(a[R].y); 69 tmp[now++] = a[R++]; 70 } 71 for(int i = l;i <= r;++i) a[i] = tmp[i]; 72 int k = tt.size(); 73 for(int i = 0;i < k;++i) update(tt[i].first,-tt[i].second); 74 } 75 int main() { 76 W = read(); 77 int tot = 0; 78 int now = 0; 79 while(1) { 80 int opt = read();if(opt == 3)break; 81 if(opt == 1) { 82 a[++tot].x = read() + 2;a[tot].y = read() + 2;a[tot].val = read(); 83 a[tot].opt = 1; 84 } 85 else { 86 int x1 = read() + 2,y1 = read() + 2,x2 = read() + 2,y2 = read() + 2; 87 a[++tot].id = ++now;a[tot].x = x1 - 1,a[tot].y = y1 - 1;a[tot].opt = 2; 88 a[++tot].id = now;a[tot].x = x2,a[tot].y = y2;a[tot].opt = 2; 89 a[++tot].id = now;a[tot].x = x1 - 1,a[tot].y = y2,a[tot].opt = 3; 90 a[++tot].id = now;a[tot].x = x2,a[tot].y = y1 - 1,a[tot].opt = 3; 91 92 } 93 } 94 cdq(1,tot); 95 for(int i = 1;i <= now;++i) printf("%d\n",ans[i]); 96 return 0; 97 }
1 /* 2 * @Author: wxyww 3 * @Date: 2019-02-14 10:15:05 4 * @Last Modified time: 2019-02-14 12:22:30 5 */ 6 #include<cstdio> 7 #include<iostream> 8 #include<cstdlib> 9 #include<cstring> 10 #include<algorithm> 11 #include<queue> 12 #include<vector> 13 #include<ctime> 14 using namespace std; 15 typedef long long ll; 16 #define int ll 17 const int N = 3000000 + 100; 18 ll read() { 19 ll x=0,f=1;char c=getchar(); 20 while(c<'0'||c>'9') { 21 if(c=='-') f=-1; 22 c=getchar(); 23 } 24 while(c>='0'&&c<='9') { 25 x=x*10+c-'0'; 26 c=getchar(); 27 } 28 return x*f; 29 } 30 struct node { 31 int id,x,y,opt,pos;//1表示修改,2表示+,3表示- 32 }a[N],tmp[N]; 33 int mx,ans[N]; 34 int tree[10000000 + 10]; 35 void update(int pos,int c) { 36 while(pos <= mx) { 37 tree[pos] += c; 38 pos += pos & -pos; 39 } 40 } 41 int query(int pos) { 42 int ret = 0; 43 while(pos >= 1) { 44 ret += tree[pos]; 45 pos -= pos & -pos; 46 } 47 return ret; 48 } 49 vector<int>tt; 50 void cdq(int l,int r) { 51 if(r <= l) return; 52 int mid = (l + r) >> 1; 53 cdq(l,mid);cdq(mid + 1,r); 54 tt.clear(); 55 int L = l,R = mid + 1,now = l; 56 while(L <= mid && R <= r) { 57 if(a[L].x <= a[R].x) { 58 if(a[L].opt == 1) update(a[L].y,1),tt.push_back(a[L].y); 59 tmp[now++] = a[L++]; 60 } 61 else { 62 if(a[R].opt == 2) ans[a[R].pos] += query(a[R].y); 63 else if(a[R].opt == 3) ans[a[R].pos] -= query(a[R].y); 64 tmp[now++] = a[R++]; 65 } 66 } 67 while(L <= mid) tmp[now++] = a[L++]; 68 while(R <= r) { 69 if(a[R].opt == 2) ans[a[R].pos] += query(a[R].y); 70 else if(a[R].opt == 3) ans[a[R].pos] -= query(a[R].y); 71 tmp[now++] = a[R++]; 72 } 73 for(int i = l;i <= r;++i) a[i] = tmp[i]; 74 int k = tt.size(); 75 for(int i = 0;i < k;++i) update(tt[i],-1); 76 } 77 int tot; 78 signed main() { 79 int n = read(),Q = read(); 80 for(int i = 1;i <= n;++i) { 81 a[++tot].x = read() + 2;a[tot].y = read() + 2; 82 mx = max(mx,a[tot].y);a[tot].opt = 1; 83 } 84 for(int i = 1;i <= Q;++i) { 85 int x1 = read() + 2,y1 = read() + 2,x2 = read() + 2,y2 = read() + 2; 86 a[++tot].x = x1 - 1,a[tot].y = y1 - 1,a[tot].opt = 2,a[tot].pos = i; 87 a[++tot].x = x2,a[tot].y = y2,a[tot].opt = 2,a[tot].pos = i; 88 a[++tot].x = x1 - 1,a[tot].y = y2,a[tot].opt = 3,a[tot].pos = i; 89 a[++tot].x = x2,a[tot].y = y1 - 1,a[tot].opt = 3,a[tot].pos = i; 90 } 91 cdq(1,tot); 92 for(int i = 1;i <= Q;++i) printf("%lld\n",ans[i]); 93 return 0; 94 }