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 << ":\n";
        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\n", 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;
}
posted @ 2019-09-23 00:03  moxin0509  阅读(265)  评论(0编辑  收藏  举报