1. 程式人生 > 實用技巧 >CDQ分治

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\)套樹狀陣列

五維偏序

動態逆序對

整體二分