最近因为牛客暑期多校的一道题涉及到了CDQ分治,于是便去学习了一下CDQ分治。

CDQ分治是以曾经的IOI选手陈丹琦命名的一种强大的算法,主要用于解决偏序问题,通过对一维进行排序(在这里说的总维度为二维),再对其它维度进行分治。

再来讲讲CDQ分治的特点,普通分治是将原问题划分为若干个子问题,每个子问题相互独立且与原问题形式相同,递归求解子问题,最后合并子问题的解得到原问题的解。而CDQ分治中,对于你每一次划分出来的两个子问题,前一个子问题用来解决后一个子问题,而不是其本身。

CDQ分治是一个非常不错的算法,不过当我们想要使用它时需要注意,题目里修改操作对询问的贡献独立,修改操作互不影响,题目允许使用离线算法

好了,CDQ分治的基本介绍就到这为止,现在我们来开始基本的CDQ分治的学习。

1.归并排序

学习CDQ分治,归并排序是不可或缺的。

直接上代码吧。

#include<bits/stdc++.h>
using namespace std;
int b[100];
void merge_sort(int l,int r,int *a){if(l==r)return;int m=(l+r)>>1;merge_sort(l,m,a);merge_sort(m+1,r,a);int t1=l,t2=m+1;for(int i=l;i<=r;i++){if((a[t1]<a[t2]&&t1<=m)||t2>r){b[i]=a[t1++];}else{b[i]=a[t2++];}}for(int i=l;i<=r;i++)a[i]=b[i];
}
int main(){int c[10]={5,6,87,8,45,7,2,1,14,3};merge_sort(0,9,c);for(int i=0;i<10;i++)cout<<c[i]<<' ';
}

也许有些人不是很理解这段代码什么逻辑,那么我便为这些人专门讲解一下吧。上述代码的总体思想是维护你每次递归划分的左区间和右区间的有序性,最后使得整个区间有序,至于怎么维护,你们仔细看代码,我们将原区间划分到不可再分即l==r(只有一个元素)时,我们需要返回到上一次划分的时候,即区间里只有两个元素,这时左区间和右区间必定有序(因为左区间和右区间都只有一个元素),然后我们对这段区间进行排序,维护该段区间的整体有序性,后续的操作也是类似的道理,至于更多的细节还请大家看代码自己领悟。

2.逆序对问题

在了解了归并排序后,我们再来讲一下CDQ分治最简单的应用——逆序对问题。

题目:给一列数a1,a2,…,an,求它的逆序对数,即有多少个有序对(i,j),使得i<j但ai>aj。1<=n<=1e6;

我们很容易找到一种特殊情况,即这一列数的左区间和右区间都是有序的,这时我们便可以通过很简单的比较求出有多少个逆序对并且不会超时,既然左区间和右区间有序,不妨设左区间为[1,k],右区间为[k+1,n],那么若是a[i]>a[j]a[i]为左区间里的一个数,a[j]为右区间的一个数),那么ans+=k-i+1,我们对左区间里的每一个数进行这样一个比较,便能求得整个区间的逆序对数。

看到这里详细很多人已经明白了,既然这样的话,那么我用归并排序维护每个递归划分的区间的有序性,同时统计这些区间的答案不就行了吗?没错,这就是CDQ分治了,通过归并排序维护有序性,再通过左区间对右区间求解。

具体怎么做,我们还是看代码吧。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+9;
int a[maxn],b[maxn],ans=0;
void CDQ(int l,int r,int *a){if(l==r)return;int m=(l+r)>>1;CDQ(l,m,a);CDQ(m+1,r,a);int t1=l,t2=m+1;for(int i=l;i<=r;i++){if(a[t1]>a[t2]&&t2<=r||t1>m){b[i]=a[t2++];ans+=m-t1+1;}else{b[i]=a[t1++];}}for(int i=l;i<=r;i++)a[i]=b[i];
}
int main(){int i,j,k,n;cin>>n;for(i=1;i<=n;i++){cin>>a[i];}CDQ(1,n,a);cout<<ans<<endl;
}

看完代码之后是不是感觉和归并排序特别像呢,没错,这段代码其实就是对归并排序进行了一些些微的改动而已,考虑了前面区间对后面区间的影响,统计了答案。

3.二维偏序问题

其实之前的逆序对问题也可是称之为二维偏序问题,只不过逆序对问题中其中一维(下标)默认是有序的,于是我们便忽略了这一维,只考虑了值这一维度。在二维偏序问题中我们经常要对其中一个维度进行排序,再对另一个维度进行CDQ分治,来得出问题的答案。

在这里给出一个比较经典的问题吧。

给定一个N个元素的序列a,对这个序列进行M次以下两种操作中的一个 
操作1:格式1 x k,把位置x的元素加上k 
操作2:格式为2 x y,求出区间[x,y]内所有元素的和

有些同学在学习树状数组时可能见过这个问题,没错,这个问题确实可以用树状数组来解决,而且还比用CDQ分治要轻松,但是,那又如何,今天我就要用CDQ分治来解决这个问题。

我们注意到这个问题有两个维度,第一个维度,操作的时间(可以理解为数组下标),第二个维度,操作的位置。时间这个维度因为我们是按照顺序加入的,所以不用管,我们只需要对位置维度进行CDQ分治就行了。

注意:我们不要忘记CDQ分治使用的前提,这道题因为有多次询问,我们需要使用离线算法

piu,直接上代码。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+9;
struct node{int type,set,val;bool operator <(const node &a){//用于排序 if(set!=a.set)return set<a.set;else return type<a.type;}
}a[3*maxn],b[3*maxn];
int ans[maxn];
void CDQ(int l,int r,node *a){if(l==r)return;int m=(l+r)>>1;CDQ(l,m,a);CDQ(m+1,r,a);int t1=l,t2=m+1,sum=0;for(int i=l;i<=r;i++){if(a[t1]<a[t2]&&t1<=m||t2>r){if(a[t1].type==1){sum+=a[t1].val;  //类似前缀合 }b[i]=a[t1++];}else{if(a[t2].type==2)ans[a[t2].val]-=sum; else if(a[t2].type==3)ans[a[t2].val]+=sum;b[i]=a[t2++];}}    for(int i=l;i<=r;i++)a[i]=b[i];
}
int main(){int i,j,k,n,m,cnt;cin>>n>>m;for(i=1;i<=n;i++){cin>>a[i].val;a[i].type=1;a[i].set=i;}cnt=n+1;int tot=1;for(i=1;i<=m;i++){int t,x,y;cin>>t>>x>>y;if(t==1){a[cnt].val=y;a[cnt].set=x;a[cnt++].type=1;}else{//离线 a[cnt].val=tot;a[cnt].type=2;a[cnt++].set=x-1;a[cnt].val=tot++;a[cnt].type=3;a[cnt++].set=y;}}CDQ(1,cnt-1,a);for(i=1;i<tot;i++)cout<<ans[i]<<endl;
}

没错,这道题还用到了一点前缀和的知识,哈哈哈哈。反正这段代码认真看吧,如果前面两个弄懂了,这个应该也不难弄懂。

4.三维偏序问题

解决了二维偏序问题,我们来看一看三维偏序问题。对于三维偏序问题,我就讲一讲大概的思路,因为我也不是很熟练。

首先,我们对一维进行排序,然后我们就可以忽略这一维度的影响了,之后我们再对另外一维进行CDQ分治。

ok,前面两个维度已经解决了,那么我们要如何解决最后一个维度呢,其中一种办法是通过树状数组解决。至于具体怎么解决我就不多说了,感兴趣的自己去百度吧。

简单入门CDQ分治(很有意思的算法)相关推荐

  1. Bzoj 2683: 简单题(CDQ分治)

    2683: 简单题 Time Limit: 50 Sec Memory Limit: 20M. Description 你有一个N*N的棋盘,每个格子内有一个整数,初始时的时候全部为0,现在需要维护两 ...

  2. CDQ分治学习及例题总结

    文章目录 1.**使用cdq分治的条件:** 2.**cdq分治的性质:** 3. **cdq使用步骤:** 4.**自己对cdq分治学习的一些感悟:** 5. cdq分治的详细讲解(转自[stdca ...

  3. 【教程】简易CDQ分治教程学习笔记

    前言 辣鸡蒟蒻__stdcall终于会CDQ分治啦!       CDQ分治是我们处理各类问题的重要武器.它的优势在于可以顶替复杂的高级数据结构,而且常数比较小:缺点在于必须离线操作. CDQ分治的基 ...

  4. 点分治+CDQ分治+整体二分全纪录

    点分治 点分治讲解 解决树上路径问题 经典例题:点分治(长度小于m的路径计数) 经典例题:点分治(聪聪可可) 经典例题:点分治(多个定值路径计数) 经典例题:点分治(采药) 经典例题:点分治+ST表+ ...

  5. CDQ分治入门 + 例题 Arnooks's Defensive Line [Uva live 5871]

    CDQ分治入门 简介 CDQ分治是一种特别的分治方法,它由CDQ(陈丹琦)神犇于09国家集训队作业中首次提出,因此得名.CDQ分治属于分治的一种.它一般只能处理非强制在线的问题,除此之外这个算法作为某 ...

  6. cdq分治(bzoj 1176: [Balkan2007]Mokia bzoj 2683: 简单题)

    CDQ分治: 本质:对询问进行分治 优点:和莫队分块一样都属于技巧,关键时刻能免去复杂的数据结构,常数小 缺点:必须离线 参考:http://blog.csdn.net/hbhcy98/article ...

  7. 【原创】从BZOJ2683 简单题中 整 CDQ分治解决三维偏序

    CDQ分治 题目描述 你有一个N*N的棋盘,每个格子内有一个整数,初始时的时候全部为0,现在需要维护两种操作: 命令 参数限制 内容 1 x y A 1<=x,y<=N,A是正整数 将格子 ...

  8. 【学习笔记】网络流算法简单入门

    [学习笔记]网络流算法简单入门 [大前言] 网络流是一种神奇的问题,在不同的题中你会发现各种各样的神仙操作. 而且从理论上讲,网络流可以处理所有二分图问题. 二分图和网络流的难度都在于问题建模,一般不 ...

  9. 机器学习 —— KNN算法简单入门

    机器学习 -- KNN算法简单入门 第1关:手动实现简单kNN算法 1 KNN算法简介 1.1 kNN 算法的算法流程 1.2 kNN 算法的优缺点 1.3 编程要求+参数解释 2. 代码实现 3. ...

最新文章

  1. Uber提出损失变化分配方法LCA,揭秘神经网络“黑盒”
  2. python爬虫之User Agent
  3. VTK:PolyData之KochanekSpline
  4. 计算机原理语言方框图,计算机原理整理原版1.10.docx
  5. SQL执行效率提升几万倍的操作详解!
  6. 解决java poi海量数据导出内存溢出问题
  7. 机器视觉对印刷业的影响
  8. 程序设计导引及在线实践之麦森数
  9. A Belief Propagation Algorithm for Multipath-Based SLAM IEEE TWC2019阅读
  10. cholesky分解java代码,实数矩阵Cholesky分解算法的C++实现
  11. 明光杂感之四:足球与情境觉知(上)
  12. 【阅读笔记】Cost Volume Pyramid Based Depth Inference for Multi-View Stereo
  13. 2010年6月 工作 计划 发奋图钱 再接再厉
  14. 均值方差模型python_python实现资产配置(2)--Blacklitterman 模型
  15. colorkey唇釉是否安全_colorkey镜面唇釉好用吗
  16. 调功器PA400X系列选型+个人理解
  17. 自学前端需要达到什么水平才能去找工作?来看看这套前端学习路线图
  18. 计算机系统基础第四篇-6 elf文件的链接
  19. oracle如何进行多列分组统计,ORACLE分组统计
  20. 记录python量化学习过程

热门文章

  1. 安装程序时出现2502 2503错误解决方法
  2. 自然语言处理--文档集数据处理 gensim corpora.Dictionary
  3. python常用的集成开发工具,python的主流开发工具
  4. e3服务器性能怎么样,Inte至强 E3-1230 V3性能怎么样?跟 i7 相比呢?
  5. Linux NAPI机制分析
  6. mysql数据库物理结构_MySQL数据库结构设计(物理设计)
  7. WIN7下网络共享设置
  8. Python入门(一) —— 编程基础
  9. nodejs之json数据
  10. 基于随机分形搜索算法的函数寻优算法