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
",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
", ans[i]);
    return 0;
}

传送门

四维偏序

时间复杂度(O(nlog^3n))

(CDQ)(CDQ)套树状数组

五维偏序

动态逆序对

整体二分

原文地址:https://www.cnblogs.com/Emcikem/p/13205313.html