CDQ分治
CDQ分治和整体二分都是基于分治思想的,把复杂的问题拆成许多可以简单求解的子问题,但必须是离线的
普通分治
一维偏序
时间复杂度\(O(nlogn)\)
逆序对,树状数组或者归并排序去求
注意用树状数组时离散化一下,即可。先初始化id,然后排完序后再赋值排名就行了
void MergeSort(int l,int r){
if(l == r)return;
int mid = (l + r) >> 1;
MergeSort(l, mid);//左边
MergeSort(mid+1,r);//右边
int i = l,j = mid + 1, t = l;//i是左边的,j是右边的
while(i <= mid && j <= r){
if(a[i] <= a[j])b[t++] = a[i++];//左边小
else b[t++] = a[j++],ans += mid - i + 1;//[i, mid]都比a[j]大
}
while(i <= mid)b[t++] = a[i++];//右边部分多
while(j <= r)b[t++] = a[j++];//左边部分多
for(int k = l;k <= r; k++)a[k] = b[k];//及时更新
}
二维偏序
时间复杂度\(O(nlogn)\)
有n个元素,每个元素有\(a_i,b_i\)两个属性,设\(f(i)\)表示满足\(a_j ≤a_i\)且\(b_j≤b_i\)且\(j≠i\)的数量。对于\(d∈[0,n)\),求满足\(f(i) = d\)的数量
首先对a进行排序,如果a相同,就按照b排序。
然后按排好的顺序,先查询[1,i]里b比自己小的,然后把自己加进去。
因为已经按照a轴排序了。那么在我查询[1,i]里b比自己小的数量时,已经保证所有的a都比自己小了。而对于前缀和查询+单点修改,可以用树状数组进行维护。
树状数组
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
int n, c[N], b[N];
struct Stars{
int x,y;
}a[N];
bool cmp(Stars a,Stars b){
if(a.x == b.x)return a.y < b.y;
return a.x < b.x;
}
int lowbit(int x){
return x & (-x);
}
void add(int i,int k){
for(; i <= N; i += lowbit(i))c[i] += k;
}
int sum(int i){
int ans = 0;
for(; i; i -= lowbit(i))ans += c[i];
return ans;
}
离散化
因为对于每个节点的下标就代表属性值,而数据范围又会很大,需要离散化
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%d%d",&a[i].x,&a[i].y),b[i] = a[i].y;
sort(b + 1, b + n + 1);
int q = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; i++)
a[i].y = lower_bound(b + 1, b + n + 1, a[i].y) - b;
sort(a + 1, a + n + 1, cmp);
维护
因为本身不能算,所以是先sum再add
正向可以求出x和y都比本身小的数的个数为\(now\)
反向可以求出x和y都比本身大的数的个数为\(n - i+now\)
for(int i = 1; i <= n; i++){
int now = sum(a[i].y);//找到y值≤自己的个数
add(a[i].y, 1);
}
如果是求满足二维偏序的i的个数
倒着维护得话,now得到的是\(x\)比自己大,但是y比自己小的个数,其中n - i表示的是\(x\)比自己大的数,那么\(n - i - now\)就是两个属性都比自己大的数
for(int i = n; i >= 1; i--){
int now = sum(a[i].y);
if(n - i - now > 0)ans++;
}
模板
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
int n, c[N], b[N];
struct Stars{
int x,y;
}a[N];
bool cmp(Stars a,Stars b){
if(a.x == b.x)return a.y < b.y;
return a.x < b.x;
}
int lowbit(int x){
return x & (-x);
}
void add(int i,int k){
for(; i <= N; i += lowbit(i))c[i] += k;
}
int sum(int i){
int ans = 0;
for(; i; i -= lowbit(i))ans += c[i];
return ans;
}
int main(){
scanf("%d",&n);
for(int i = 1; i <= n; i++)scanf("%d%d",&a[i].x,&a[i].y),b[i] = a[i].y;
sort(b + 1, b + n + 1);
int q = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; i++)
a[i].y = lower_bound(b + 1, b + n + 1, a[i].y) - b;
sort(a + 1, a + n + 1, cmp);
for(int i = 1; i <= n; i++){
int now = sum(a[i].y);
add(a[i].y, 1);
}
return 0;
}
扩展
在一个地图上有n个点,m个查询,查询\((x_1,y_1),(x_2,y_2)\)这个矩阵里有几个点。
把每个查询(分成4块,最后利用前缀和思想求)和每个点都加入query结构体,然后排序求,如果是加入的点,opt为1,查询则不加。排序,第一关键词x,第二关键词y,最后如果是查询就放在点的后面
#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
const int maxq = 3e6 + 5;
struct Ques{
int x,y,opt,id;//opt表示点的类型,1为修改点,0为询问点
bool operator < (const Ques &b)const{//排序
return x == b.x ? (y == b.y ? opt : y < b.y) : x < b.x;
}
}ques[maxq],tmp[maxq];
int ques_tot,ans_tot,ans[maxq];
void cdq(int a,int b){//二维偏序
if(a == b)return;
int mid = (a + b) >> 1;
cdq(a,mid),cdq(mid + 1,b);
int posa = a,posb = mid + 1,pos = a,tot = 0;
while(posa <= mid && posb <= b){
if(ques[posa].y <= ques[posb].y)tot += ques[posa].opt,tmp[pos++] = ques[posa++];
else ans[ques[posb].id] += tot,tmp[pos++] = ques[posb++];
}
while(posa <= mid)tmp[pos++] = ques[posa++];
while(posb <= b)ans[ques[posb].id] += tot,tmp[pos++] = ques[posb++];
for(int i = a;i <= b;i++)ques[i] = tmp[i];
}
int n,m;
int main(){
scanf("%d %d",&n,&m);
for(int x,y,i = 1;i <= n;i++)
scanf("%d %d",&x,&y),ques[++ques_tot] = Ques{x,y,1,0};
for(int a,b,c,d,i = 1;i <= m;i++){//拆点
scanf("%d %d %d %d",&a,&b,&c,&d);
ques[++ques_tot] = Ques{c,d,0,++ans_tot};
ques[++ques_tot] = Ques{c,b - 1,0,++ans_tot};
ques[++ques_tot] = Ques{a - 1,d,0,++ans_tot};
ques[++ques_tot] = Ques{a - 1,b - 1,0,++ans_tot};
}
sort(ques + 1,ques + 1 + ques_tot);
cdq(1,ques_tot);
for(int i = 1;i + 3 <= ans_tot;i += 4)
printf("%d\n",ans[i] - ans[i + 1] - ans[i + 2] + ans[i + 3]);
return 0;
}
三维偏序
时间复杂度\(O(nlog^2n)\)
有n个元素,第\(i\)个元素有\(a_i,b_i,c_i\)三个属性,设\(f(i)\)表示满足\(a_j ≤ a_i\)且\(b_j≤b_i\)且\(c_j≤c_i\)且\(i≠j\)的数量。对于\(d∈[0,n)\),求\(f(i)=d\)的数量
先按每个元素的a排序,然后第二维用归并排序,第三维用树状数组(需要离散化)
结构体里a,b,c是三维,w是重复的个数,f是\(f(i)\)的值
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int c[N << 1], ans[N], n, b[N], q;
struct Stars{
int a,b,c,w,f;//a一维,b二维,c三维,w表示三个维度都相同的个数,f是三维都≤本身的个数
}e[N], t[N];
bool cmp(Stars a,Stars b){
if(a.a == b.a && a.b == b.b)return a.c < b.c;
if(a.a == b.a)return a.b < b.b;
return a.a < b.a;
}
int lowbit(int x){
return x & (-x);
}
void add(int i,int k){
for(; i <= q; i += lowbit(i))c[i] += k;
}
int sum(int i){
int ans = 0;
for(; i; i -= lowbit(i))ans += c[i];
return ans;
}
void CDQ(int l,int r){
if(l == r)return;
int mid = (l + r) >> 1;
CDQ(l, mid);CDQ(mid + 1, r);
int p = l, q = mid + 1,tot = l;
while(p <= mid && q <= r){
if(e[p].b <= e[q].b)add(e[p].c,e[p].w),t[tot++] = e[p++];
else e[q].f += sum(e[q].c),t[tot++] = e[q++];//[l,p]的第二维比q的第二维小
}
while(p <= mid)add(e[p].c,e[p].w),t[tot++] = e[p++];
while(q <= r)e[q].f += sum(e[q].c),t[tot++] = e[q++];//[l,mid]的第二维都比q的第二维小
for(int i = l; i <= mid; i++)add(e[i].c, -e[i].w);//清空数组数组
for(int i = l; i <= r; i++)e[i] = t[i];
}
int main(){
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c),e[i].w = 1, b[i] = e[i].c;
sort(b + 1, b + n + 1);
q = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; i++)
e[i].c = lower_bound(b + 1, b + q + 1, e[i].c) - b;
sort(e + 1,e + n + 1,cmp);
int cnt = 1;
for(int i = 2; i <= n; i++){
if(e[i].a == e[cnt].a && e[i].b == e[cnt].b && e[i].c == e[cnt].c)e[cnt].w++;
else e[++cnt] = e[i];
}
CDQ(1, cnt);
for(int i = 1; i <= cnt; i++)ans[e[i].f + e[i].w - 1] += e[i].w;//e[i]的答案就是≤自己的个数+与自己值相同的个数-1(本身)
for(int i = 0; i < n; i++)printf("%d\n", ans[i]);
return 0;
}
四维偏序
时间复杂度\(O(nlog^3n)\)
\(CDQ\)套\(CDQ\)套树状数组