• CDQ分治学习笔记


    CDQ分治学习笔记

    什么是CDQ分治呢?
    CDQ分治,从一维的角度来考虑问题,那么就是归并排序
    那么为什么叫CDQ分治不叫归并排序呢?
    

    <1>

    1.我们考虑一个题目,给出一个数组,有n个元素,涉及m次操作,其中有单点更新操作与区间查询
    有点经验的同学已经想到了使用树状数组/线段树来解决问题,如果不使用这两种数据结构能否解决
    问题呢?
    我们先来考虑insert 操作 与 query 操作
    我们考虑一个问题,没错insert会影响后续的query操作
    那么query出来的结果应该是 前面insert对后面造成的影响
    我们可以先获取所有的操作
    然后通过归并排序,从低往上更新,然后可得对应查询的结果
    附上例题
    

    例题1
    【思路】:我们可以先考虑怎么处理插入,查询的操作,通过前面的分析我们可以知道插入肯定会影响后序的查询
    那么我们要怎么实现区间呢?现将区间分成两个点【l,r】,可以变成对应的前缀和sum[r] - sum[l - 1]
    可以处理一个结构题

    struct NODE{
        int type, idx;
        ll val;
        ///type代表数据的类型:是查询节点,还是插入节点 查询节点分为两种 一个是r, 一个是l
        ///对应1 3 2
        ///idx代表编号
        ///val是代表插入的更新的值,或者当前是第几个查询
    }
    
    然后可以进行CDQ分治,怎么分治呢?
    考虑type的种类,更新肯定要在前面,然后考虑idx如果种类相同idx肯定要在前面
    然后使用一个sum来保存当前的值
    详细看代码理解
    
    void CDQ(int l, int r){
        if(l + 1 < r){
            return ;
        }///当前只有一个元素
        int mid = (l + r) >> 1;
        CDQ(l, mid);
        CDQ(mid, r);
        int p, q;
        p = l, q = r;
        long long sum = 0;
        vector<NODE>vec;
        while(p < mid && q < r)///注意是[)区间
        {
            if(arr[p] < arr[q]){
                sum += arr[p].val;
                vec.push_back(arr[p]);
                p ++;
            }
            else{
                if(arr[q].type == 2) ans[arr[q].val] -= sum;
                else if(arr[q].type == 3) ans[arr[q].val] += sum;
                vec.push_back(arr[q]);
                q ++;
            }
        }
        while(p < mid){
            vec.push_back(arr[p ++]);
        }
        while(q < r){
            if(arr[q].type == 2) ans[arr[q].val] -= sum;
            else if(arr[q].type == 3) ans[arr[q].val] += sum;
            vec.push_back(arr[q]);
            q ++;
        }
        for(int i = 0; i < vec.size(); i ++){
            arr[l + i] = vec[i];
        }
        /**
         * 一层一层向上递归
         * 然后把每次的贡献加上去
         * 这样就能保证答案是对的
         * 因为每次都是左区间作用于右区间
         * 然后因为是1 1向上去递归的
         * 所以所有的影响都会被考虑
         * 时间复杂度是O(nlogn)
         * **/
    }
    

    附上代码:

    #include <bits/stdc++.h>
    using namespace std;
    /*
    CDQ分治
    */
    const int MAXN = 50000 + 5;
    int n, m;
    typedef long long LL;
    struct query {
        int type, idx;
        LL val;
        friend bool operator<(const query& a, const query& b) {
            return a.idx == b.idx ? a.type < b.type : a.idx < b.idx;
        }
    } arr[MAXN << 2];
    query temp[MAXN << 2];
    LL ans[MAXN << 2];
    void CDQ(int l, int r){
        if(r - l <= 1){
            return ;
        }
        int mid = (l + r) >> 1;
        CDQ(l, mid);
        CDQ(mid, r);
        int p = l, q = mid, tot = 0;
        LL sum = 0;
        while(p < mid && q < r){
            if(arr[p] < arr[q]){
                if(arr[p].type == 1) sum += arr[p].val;
                temp[tot ++] = arr[p ++];
            }
            else{
                if(arr[q].type == 2) ans[arr[q].val] -= sum;
                else if(arr[q].type == 3) ans[arr[q].val] += sum;
                temp[tot ++] = arr[q ++]; 
            }
        }
        while(p < mid){
            temp[tot ++] = arr[p ++];
        }
        while(q < r){
            if(arr[q].type == 2) ans[arr[q].val] -= sum;
            else if(arr[q].type == 3) ans[arr[q].val] += sum;
            temp[tot ++] = arr[q ++]; 
        }
        for(int i = 0; i < tot; i ++){
            arr[l + i] = temp[i];
        }
    }
    vector<int>vec;
    int main() {
        ios::sync_with_stdio(false);
        int T, cnt = 0;
        cin >> T;
        int cases = 0;
        while(T --){
            memset(ans, 0, sizeof(ans));
            vec.clear();
            int n;
            cin >> n;
            cnt = 0;
            for(int i = 1; i <= n; i ++){
                int num;
                cin >> num;
                arr[cnt].idx = i, arr[cnt].type = 1, arr[cnt ++].val = num;
            }
            int ans_num = 0;
            while(1){
                string str;
                cin >> str;
                if(str == "Query"){
                    int l, r;
                    cin >> l >> r;
                    vec.push_back(ans_num);
                    arr[cnt].idx = r, arr[cnt].type = 3, arr[cnt ++].val = ans_num;
                    arr[cnt].idx = l - 1, arr[cnt].type = 2, arr[cnt ++].val = ans_num ++;
                }
                else if(str == "Add"){
                    int c, value;
                    cin >> c >> value;
                    arr[cnt].idx = c, arr[cnt].type = 1, arr[cnt ++].val = value;
                }
                else if(str == "Sub"){
                    int c, value;
                    cin >> c >> value;
                    arr[cnt].idx = c, arr[cnt].type = 1, arr[cnt ++].val = -value;
                }
                else{
                    break;
                }
            }
            CDQ(0, cnt);
            cout << "Case " << ++cases << ":
    ";
            for(int i = 0; i < vec.size(); i ++){
                cout << ans[vec[i]] << endl;
            }
        }
        return 0;
    }
    
    

    <2>

    2.刚刚考虑的一维的,那我们来考虑使用CDQ分治来解决二维偏序问题
    先附上例题
    

    例题2
    [思路]:这个题目其实可以使用离线 + 线段树 + 离散化来ac这个问题,但是我们考虑使用CDQ分治来处理问题
    我们可以考虑把其变成一个三维的,加上一个维度type代表是插入还是查询,因为插入一个就查询一个,所以
    点的个数就*2了,然后先按照

     friend bool operator<(const NODE &a, const NODE &b){
            if(a.x != b.x){
                return a.x < b.x;
            }
            if(a.type != b.type){
                return a.type < b.type;
            }
            return a.y < b.y;
        }
    
    进行排序,这样我们可以保证归并之前,左边的区间[l,mid)的x的值肯定小于[mid,r)的值,
    左边的更新可能会对右边的更新有贡献
    其实一次查询肯定是有左边对右边的贡献,你可能会问那么[mid, r)这个区间就失去了对本区间的贡献了吗?
    其实不是,因为我们肯定是分治递归到底才进行计算的,所以[mid, r)区间的贡献在底已经先计算过了
    那么考虑CDQ分治的过程,考虑使用y来排序,若相同着考虑type,肯定是先更新后查询
    注意每次使用树状数组来维护y区间的数的个数,当type == 1的时候查询的时候,就可以直接查询query(l, r)
    type == 0的时候,可以考虑add(y, 1)
    当更新完答案的时候,肯定要把树状数组清空,因为每一次左右区间都会发生变化,所以上一次的树状数组就是无效的
    只有[l, mid)才会出现add的操作,所以只要把[l, mid) 使用add(y, -1)
    附上代码:
    
    #include <bits/stdc++.h>
    #pragma GCC optimize(2)
    using namespace std;
    int n, m;
    const int MAXN = 3e5 + 5;
    struct tree_arr{
        int tree[MAXN];
        inline int lowbit(int x){
            return x & -x;
        }
        inline void add(int k, int val){
            while(k <= n + m){
                tree[k] += val;
                k += lowbit(k);
            }
        }
        inline int sum(int k){
            int sum = 0;
            while(k){
                sum += tree[k];
                k -= lowbit(k);
            }
            return sum;
        }
        inline int query(int l, int r){
            return sum(r) - sum(l - 1);
        }
    }t_a;
    struct NODE{
        int x, y, type, idx;
        friend bool operator<(const NODE &a, const NODE &b){
            if(a.x != b.x){
                return a.x < b.x;
            }
            if(a.type != b.type){
                return a.type < b.type;
            }
            return a.y < b.y;
        }
    }arr[MAXN];
    vector<int>vec;
    int ans[MAXN];
    bool cmp(const NODE &a, const NODE &b){
        if(a.y != b.y)  return a.y < b.y;
        if(a.type != b.type)    return a.type < b.type;
        return a.x < b.x;
    }
    NODE temp[MAXN];
    void CDQ(int l, int r){
        if(l + 1 >= r)   return;
        int mid = (l + r) >> 1;
        CDQ(l, mid);
        CDQ(mid, r);
        int cnt = 0;
        int p, q;
        p = l, q = mid;
        while(p < mid && q < r){
            if(arr[p].y <= arr[q].y){
                if(arr[p].type == 0)  
                t_a.add(lower_bound(vec.begin(), vec.end(), arr[p].y) - vec.begin() + 1, 1);
                temp[cnt ++] = arr[p];
                p ++;
            }
            else{
                if(arr[q].type == 1) 
                ans[arr[q].idx] += t_a.query(1, lower_bound(vec.begin(), vec.end(), arr[q].y) - vec.begin() + 1);
                temp[cnt ++] = arr[q];
                q ++;
            }
        }
        while(p < mid){
            if(arr[p].type == 0) 
            t_a.add(lower_bound(vec.begin(), vec.end(), arr[p].y) - vec.begin() + 1, 1);
            temp[cnt ++] = arr[p];
            p ++;
        }
        while(q < r){
            if(arr[q].type == 1) 
            ans[arr[q].idx] += t_a.query(1, lower_bound(vec.begin(), vec.end(), arr[q].y) - vec.begin() + 1);
            temp[cnt ++] = arr[q];
            q ++;
        }
        for(int i = l; i < mid; i ++){
            if(arr[i].type == 0) 
            t_a.add(lower_bound(vec.begin(), vec.end(), arr[i].y) - vec.begin() + 1, -1);
        }
        for(int i = 0; i < cnt; i ++){
           arr[l + i] = temp[i];
        }
        return ;
    }
    int main(){
        scanf("%d%d", &n, &m);
        for(int i = 0; i < n; i ++){
            scanf("%d%d", &arr[i].x, &arr[i].y);
            vec.push_back(arr[i].y);
            arr[i].type = 0;
        }
        for(int i = 0; i < m; i ++){
            scanf("%d%d", &arr[n + i].x, &arr[n + i].y);
            arr[n + i].type = 1;
            arr[n + i].idx = i;
            vec.push_back(arr[i].y);
        }
        sort(vec.begin(), vec.end());
        vec.erase(unique(vec.begin(), vec.end()), vec.end());
        sort(arr, arr + (n + m));
        CDQ(0, n + m);
        for(int i = 0; i < m; i ++){
            printf("%d
    ", ans[i]);
        }
        return 0;
    }
    

    <3> CDQ分治处理三维偏序问题

    例题3
    什么是三维偏序?三维偏序就是一个三维空间询问区间内的点的个数或者是点的
    总和价值,时间复杂度是一个O(nlog(n)log(n))
    首先我们先考虑一个点<x, y, z>
    首先我们先考虑如果分为两个区间,保证左边的x小于右边的x
    那么我们只要考虑左边的点对右边的点的贡献,判断y的大小
    如果把对应的z所构造成的线段树或者树状数组
    进行更新,其他跟上面相似

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 50005;
    const int MAXM = 100005;
    struct Point{
        int x, y, z, id;
        friend bool operator<(const Point &a, const Point &b){
            if(a.x != b.x)
            return a.x < b.x;
            if(a.y != b.y)
            return a.y < b.y;
            return a.z < b.z;
        }
    }arr[MAXN];
    int ans[MAXN];
    bool cmp(const Point &a, const Point &b){
        if(a.y != b.y)
        return a.y < b.y;
        return a.z < b.z;
    }
    struct Segtree{
        struct seg_node{
            int l, r, sum;
        }tree[MAXM << 2];
        void build(int root, int l, int r){
            tree[root].l = l, tree[root].r = r;
            if(l == r){
                tree[root].sum = 0;
                return ;
            }
            int mid = (l + r) >> 1;
            build(root << 1, l, mid);
            build(root << 1 | 1, mid + 1, r);
            tree[root].sum = tree[root << 1].sum + tree[root << 1 | 1].sum;
            return ;
        }
        void update(int root, int cnt, int val){
            if(tree[root].l == tree[root].r){
                tree[root].sum += val;
                return ;
            }
            int mid = (tree[root].l + tree[root].r) >> 1;
            if(cnt <= mid){
                update(root << 1, cnt, val);
            }
            else{
                update(root << 1 | 1, cnt, val);
            }
            tree[root].sum = tree[root << 1].sum + tree[root << 1 | 1].sum;
            return ;
        }
        int query(int root, int l, int r){
            if(tree[root].l >= l && tree[root].r <= r){
                return tree[root].sum;
            }
            int mid = (tree[root].l + tree[root].r) >> 1;
            if(r <= mid){
                return query(root << 1, l, r);
            }
            else if(l > mid){
                return query(root << 1 | 1, l, r);
            }
            else{
                return query(root << 1, l, mid) + query(root << 1 | 1, mid + 1, r);
            }
        }
    }seg;
    void CDQ(int l, int r){///[)
        if(l + 1 >= r){
            return ;
        }
        int mid = (l + r) >> 1;
        int p, q;
        p = l, q = mid;
        CDQ(l, mid);
        CDQ(mid, r);
        sort(arr + l, arr + mid, cmp);
        sort(arr + mid, arr + r, cmp);
        while(p < mid && q < r){
            if(arr[p].y <= arr[q].y){
                seg.update(1, arr[p].z, 1);
                p ++;
            }
            else{
                ans[arr[q].id] += seg.query(1, 1, arr[q].z);
                q ++;
            }
        }
        while(p < mid){
            seg.update(1, arr[p].z, 1);
            p ++;
        }
        while(q < r){
            ans[arr[q].id] += seg.query(1, 1, arr[q].z);
            q ++;
        }
        for(int i = l; i < mid; i ++){
            seg.update(1, arr[i].z, -1);
        }
        return ;
    }
    int main(){
        ios::sync_with_stdio(false);
        int n;
        cin >> n;
        for(int i = 1; i <= n; i ++){
            cin >> arr[i].x >> arr[i].y >> arr[i].z;
            arr[i].id = i;
        }
        seg.build(1, 1, 100001);
        sort(arr + 1, arr + n + 1);
        CDQ(1, n + 1);
        for(int i = 1; i <= n; i ++){
            cout << ans[i] << endl;
        }
        return 0;
    }
    
  • 相关阅读:
    android 开机启动
    android 禁用home键盘
    android 获取各种窗体高度
    android 横竖屏切换
    android 还原短信
    android dp和px之间转换
    android BitMap、Drawable、inputStream及byte[] 互转
    手机卫士项目
    Android01_Android入门
    Android02_Activity
  • 原文地址:https://www.cnblogs.com/qq136155330/p/11570075.html
Copyright © 2020-2023  润新知