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;
}