前言:
说实话我对于这种没有固定板子,变化多端的算法实在是非常头疼的
但是不学不行,这也是一种很重要的思伟方式
所以趁着这几天的心情比较好(快要放大周),赶紧学一波~

鸣谢:tham,stdcall

CDQ分治,传说中是一个♀神犇创造的算法
在了解这种算法之前,我们有必要了解一下一种基本的思想:分治

知识储备:分治

  • 分治介绍
    分而治之,将原问题不断划分成若干个子问题,直到子问题规模小到足以直接解决
    子问题间互相独立且原问题形式相同,递归求解这些子问题,然后将各子问题的解合并得到原问题的解
  • 一般步骤
    • 划分 Divide
      将原问题划分成若干子问题,子问题间互相独立且与原问题形式相同
    • 解决 Conquer
      递归解决子问题(递归是彰显分治优势的工具,仅仅进行一次分治策略也许看不出优势,但递归划分到子问题规模足够小,子问题的解可用常数时间解决)
    • 合并 Merge
      将各子问题的解合并得到原问题的解
  • 时间复杂度
    • 直观估计

      • 分治由以上三部分构成,整体时间复杂度则由这三部分的时间复杂度之和构成
      • 由于递归,最终的子问题变得极为简单,以至于其时间复杂度在整个分治策略上的比重微乎其微

分治的一个经典例子就是归并求逆序对
简单叙述一下算法:
利用归并把序列对半分,在一般的归并过程中,我们在前后两部分各设两个指针,按照大小顺序合并成一个序列
我们要做的就是在这个过程中记录逆序对个数
什么情况下会有逆序对呢?
无非是在前面的数比在后面的数大(翻译过来:在前半部分的数比在后半部分的数大)
设前半部分的指针为t1,后半部分的指针为t2
如果a[t1] > a[t2],那么t1~mid的元素一定都比t2大,一定都可以与t2形成逆序对

ans+=(mid-t1+1)+1

#include<cstdio>
#include<iostream>
#include<cstring>using namespace std;const int N=100010;
int a[N],b[N],n,ans=0; void merge(int l,int r)
{if (l==r) return;int mid=(l+r)>>1;merge(l,mid);merge(mid+1,r);int t1=l; int t2=mid+1;for (int i=l;i<=r;i++){if ((t1<=mid&&a[t1]<=a[t2])||t2>r) {b[i]=a[t1];t1++;}else{b[i]=a[t2]; ans+=(mid-t1+1);t2++;} }for (int i=l;i<=r;i++) a[i]=b[i];
}int main()
{scanf("%d",&n);for (int i=1;i<=n;i++) scanf("%d",&a[i]);merge(1,n);printf("%d",ans);return 0;
} 

我为什么要介绍这个呢?
因为归并就是一个最简单的分治问题
我们在合并两个子区间的时候,要考虑到左边区间的对右边区间的影响
即,我们每次从右边区间的有序序列中取出一个元素的时候,要把“以这个元素结尾的逆序对的个数”(左边区间有多少个元素比他大)
这是一个典型的CDQ分治的过程

CDQ分治

CDQ分治是我们处理各类问题的重要武器
它的优势在于可以顶替复杂的高级数据结构,而且常数比较小
缺点在于必须离线操作

二维偏序问题

上面介绍了归并求逆序对的经典问题,我们由此引入二维偏序问题:
给定N个有序对(a,b),求对于每个(a,b),满足a0 < a且b0 < b的有序对(a0,b0)有多少个

在归并求逆序对的时候,实际上每个元素是用一个有序对(a,b)表示的,
其中a表示数组中的位置,b表示该位置对应的值
我们求的就是“对于每个有序对(a,b),有多少个有序对(a0,b0)满足a0 < a且b0 > b”,这就是一个二维偏序问题

注意到在求逆序对的问题中,a元素是默认有序的,即我们拿到元素的时候,数组中的元素是默认从第一个到最后一个按顺序排列的,所以我们才能在合并子问题的时候忽略a元素带来的影响
因为我们在合并两个子问题的过程中,左边区间的元素一定出现在右边区间的元素之前,即左边区间的元素的a都小于右边区间元素的a

那么对于二维偏序问题,我们在拿到所有有序对(a,b)的时候,先把a元素从小到大排序
这时候问题就变成了“求顺序对”,因为a元素已经有序,可以忽略a元素带来的影响,和“求逆序对”的问题是一样的。

考虑二维偏序问题的另一种解法,用树状数组代替CDQ分治,即常用的用树状数组求顺序对
在按照a元素排序之后,我们对于整个序列从左到右扫描,每次扫描到一个有序对,求出“扫描过的有序对中,有多少个有序对的b值小于当前b值”
然而当b的值非常大的时候,空间和时间上就会吃不消,便可以用CDQ分治代替,就是我们所说的“顶替复杂的高级数据结构”

二维偏序问题的拓展

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

这是一个经典的树状数组问题
但是我们就是要没事找事,我们用CDQ分治解决它——带修改和询问的问题

我们把ta转化成一个二维偏序问题,每个操作用一个有序对(a,b)表示,其中a表示操作的时间,b表示操作的位置,时间是默认有序的,所以我们在合并子问题的过程中,就按照b从小到大的顺序合并。

首先我们把原数列和1操作都看作是修改操作
询问操作[l,r]我们拆成两个:l-1,r
因为我们询问的是一个区间和,一般的思路就是前缀和相减(我们需要具备这样的思维)
实际上我们这道题也可以这样,我们按照时间顺序进行修改
记录前缀和,当遇到l-1的标记时,我们减去sum(l-1)
遇到r标记时,询问的处理就完成了

具体流程:

  • 按照id(插入位置)归并排序
  • 进行左区间的修改
  • 统计右区间的询问

需要注意的是:

  • 在合并的时候,我们只处理左区间的修改,只统计右区间的查询
    因为左区间的修改一定可以影响右区间的查询
    这就体现出了CDQ分治的基本思想了
  • 我们把所有操作都记录到了一个数组中,所以数组的大小至少要开到500000*3
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long longusing namespace std;const int N=5000010;
int n,m,totx=0,tot=0;     //totx是操作的个数,tot询问的编号 struct node{int type,id;ll val;bool operator < (const node &a) const   //重载运算符,优先时间排序 {if (id!=a.id) return id<a.id;else return type<a.type;}
};
node A[N],B[N];
ll ans[N];void CDQ(int L,int R)
{if (L==R) return;int M=(L+R)>>1;CDQ(L,M);CDQ(M+1,R);int t1=L,t2=M+1;ll sum=0; for (int i=L;i<=R;i++){if ((t1<=M&&A[t1]<A[t2])||t2>R) //只修改左边区间内的修改值{if (A[t1].type==1) sum+=A[t1].val;   //sum是修改的总值B[i]=A[t1++]; }else                         //只统计右边区间内的查询结果{if (A[t2].type==3) ans[A[t2].val]+=sum;else if (A[t2].type==2) ans[A[t2].val]-=sum;B[i]=A[t2++];}}for (int i=L;i<=R;i++) A[i]=B[i];
}int main()
{scanf("%d%d",&n,&m);for (int i=1;i<=n;i++){tot++;A[tot].type=1; A[tot].id=i;            //修改操作 scanf("%lld",&A[tot].val);}for (int i=1;i<=m;i++){int t;scanf("%d",&t);tot++;A[tot].type=t; if (t==1)scanf("%d%lld",&A[tot].id,&A[tot].val);else{int l,r;scanf("%d%d",&l,&r);totx++; A[tot].val=totx; A[tot].id=l-1;    //询问的前一个位置 tot++; A[tot].type=3; A[tot].val=totx; A[tot].id=r;  //询问的后端点 }}CDQ(1,tot);for (int i=1;i<=totx;i++) printf("%lld\n",ans[i]);return 0;
}

三维偏序问题

给定N个有序三元组(a,b,c),求对于每个三元组(a,b,c),有多少个三元组(a0,b0,c0)满足a0 < a且b0 < b且c0 < c
不用CDQ的算法,我们就不说了(太麻烦了)
类似二维偏序问题,先按照a元素从小到大排序,这样我们就可以忽略a元素的影响
然后CDQ分治,按照b元素从小到大进行归并排序
哪c元素我们要怎么处理呢?
这时候比较好的方案就是借助权值树状数组,
每次从左边取出三元组(a,b,c),根据c值在树状数组中进行修改
从右边的序列中取出三元组(a,b,c)时,在树状数组中查询c值小于(a,b,c)的三元组的个数
注意,每次使用完树状数组要把树状数组清零

三维偏序问题的拓展

平面上有N个点,每个点的横纵坐标在[0,1e7]之间,有M个询问,每个询问为查询在指定矩形之内有多少个点,矩形用(x1,y1,x2,y2)的方式给出,其中(x1,y1)为左下角坐标,(x2,y2)为右上角坐标

把每个点的位置变成一个修改操作,用三元组(时间,横坐标,纵坐标)来表示,把每个查询变成二维前缀和的查询
这样对于只有位于询问的左下角的修改,才对询问有影响
操作的时间是默认有序的,分治过程中按照横坐标从小到大排序,用树状数组维护纵坐标的信息

CDQ分治【分治(真得头疼)相关推荐

  1. bzoj2683:简单题(树状数组套CDQ分分治)

    CDQ(陈丹琦)分治 CDQ显然是一个人的名字(2008NOI金牌选手陈丹琦) 这种离线的分治算法在算法界被称为"CDQ分治".  首先回忆一下归并排序的分治, 它的操作是将数组二 ...

  2. BZOJ 1176([Balkan2007]Mokia-CDQ分治-分治询问)

    1176: [Balkan2007]Mokia Time Limit: 30 Sec   Memory Limit: 162 MB Submit: 185   Solved: 94 [ Submit] ...

  3. 解决安卓模拟器没有网络的问题,刚开的做RN的我遇见这个问题真很头疼,所以希望看到我这篇文章能够解决。

    简单来说 模拟没有网络一般是 dns 得问题 什么是dns 我这里简单得解释一下, 放在文章最后 1. 解决方案 首先 在你 安装SDK 得目录下 去找到 tools 文件夹 在目录下输入cmd如下图 ...

  4. 分治、CDQ分治小结

    分治.CDQ分治小结 A Summary for Divide and Conquer 0. Anouncement 本文部分图片以及部分内容来自互联网,内容过多就不一一注明出处了,冒犯之处还请海涵. ...

  5. 分治、CDQ分治小结(need to be updated)

    分治.CDQ分治小结 A Summary for Divide and Conquer 0. Anouncement 本文部分图片以及部分内容来自互联网,内容过多就不一一注明出处了,冒犯之处还请海涵. ...

  6. Algorithms_算法思想_递归分治

    文章目录 引导案例 递归的定义 什么样的问题可以用递归算法来解决 递归如何实现以及包含的算法思 递归的公式 斐波那契数列代码实现 递归的时间复杂度和空间复杂度 递 与 归 递归的优化 优化方式一:不使 ...

  7. 不止代码:循环比赛(分治)

    循环比赛日程表(match) [问题描述] 解析 dfs或分治 分治可以不断递归4个小正方形 左上右下为前一半,左下右上后一半 dfs就很无脑了 代码 #include<cstdio> # ...

  8. 分治——Secret Cow Code S(洛谷 P3612)

    题目选自洛谷P3612 梳理题意 给定一个字符串,每次将其最后一个字符移到最前方,形成的新串接到原串后作为下一次操作的字符串 现询问第 N 个位置的字符 简要分析 看一眼数据范围,N<10^18 ...

  9. 快速排序——主要思想是分治

    快速排序--主要思想是分治 ​ 分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同.求出子问题的解,就可得到原问题的解.即一种分目标完成程序算法, ...

最新文章

  1. 机器人水库涵洞检测_能给眼睛打针、可水下搜索救援,手术机器人水下机器人将亮相服贸会...
  2. sublime Text3 设置多个浏览器预览
  3. TF学习——TF数据读取:TensorFlow中数据读这三张图片的5个epoch +把读取的结果重新存到read 文件夹中
  4. Long Number
  5. unity3d 随机生成地形之随机山脉
  6. Django视图(二)
  7. wordpress去掉自带的logo或者左侧栏的菜单方法
  8. apt-get的更新源
  9. 用c语言编译二叉树,C语言 数据结构平衡二叉树实例详解
  10. Linux之JDK安装
  11. html 样式初始化,css样式初始化
  12. 线程同步:喂,SHE
  13. highslide图片查看特效
  14. 小凡Dynamips 虚拟pc的使用
  15. 什么是IP地址、IP协议?
  16. CF#552div3题解
  17. python的OOP机制
  18. Qt 一个简单的基于mplayer的视频播放器
  19. 趣图:gif PostgreSQL MySQL 从删库到跑路
  20. JavaScript---网络编程(8)-DHTML技术演示(1)

热门文章

  1. 用 Unity easyAR 开发 AR 发布 Android ios遇到的问题(包括easyAR recording 录屏 用法和问题)(图文详情)
  2. php closure 类,PHP中Closure类详解
  3. 【CentOS】Hive安装
  4. 微信小程序使用云函数进行RSA进行加密解密
  5. 交通元宇宙主题汇总(附链接)
  6. MTSEA-X-Biotin,353754-95-7,生物素基己酰氨基乙基甲烷硫代磺酸酯
  7. 挂靠资质施工,可否要求发包人支付工程款
  8. Pytho基础 第三章
  9. 数学分析 定积分的应用(第10章)
  10. GPRM/GNRMC定位信息的读取与解析