zoukankan      html  css  js  c++  java
  • 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)套树状数组

    五维偏序

    动态逆序对

    整体二分

  • 相关阅读:
    XML相关知识点
    MLPlatform开发日志
    1.0 es6 箭头函数
    基本数学概念
    4.4 thymeleaf使用补充
    vim操作手册
    eclise创建后台项目
    正则表达式
    数据库隔离级别
    1. gradle的使用教程
  • 原文地址:https://www.cnblogs.com/Emcikem/p/13205313.html
Copyright © 2011-2022 走看看