CDQ分治(初步入门)
CDQ分治
CDQ分治,传说中是一个神犇创造的算法。
在了解这种算法之前,我们有必要了解一下一种基本的思想:分治。
分治介绍
分而治之,将原问题不断划分成若干个子问题,直到子问题规模小到足以直接解决
子问题间互相独立且原问题形式相同,递归求解这些子问题,然后将各子问题的解合并得到原问题的解
一般步骤
划分 Divide
将原问题划分成若干子问题,子问题间互相独立且与原问题形式相同
解决 Conquer
递归解决子问题(递归是彰显分治优势的工具,仅仅进行一次分治策略也许看不出优势,但递归划分到子问题规模足够小,子问题的解可用常数时间解决)
合并 Merge
将各子问题的解合并得到原问题的解
时间复杂度
直观估计
分治由以上三部分构成,整体时间复杂度则由这三部分的时间复杂度之和构成
由于递归,最终的子问题变得极为简单,以至于其时间复杂度在整个分治策略上的比重微乎其微。
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 long
using 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分治(初步入门)的更多相关文章
- bzoj3262 陌上花开 cdq分治(入门)
题目传送门 思路:cdq分治处理偏序关系的模板题,主要就是学cdq分治吧,还在入门中. 代码其实也很好理解,记得树状数组操作的上限是 z的最大值,不是n的最大值,这个细节wa了好久. #include ...
- COGS 577 蝗灾 [CDQ分治入门题]
题目链接 昨天mhr神犇,讲分治时的CDQ分治的入门题. 题意: 你又一个w*w正方形的田地. 初始时没有蝗虫. 给你两个操作: 1. 1 x y z: (x,y)这个位置多了z只蝗虫. 2. 2 x ...
- 【题解】Radio stations Codeforces 762E CDQ分治
虽然说好像这题有其他做法,但是在问题转化之后,使用CDQ分治是显而易见的 并且如果CDQ打的熟练的话,码量也不算大,打的也很快,思维难度也很小 没学过CDQ分治的话,可以去看看我的另一篇博客,是CDQ ...
- CDQ分治 三维偏序
这应该是一道CDQ分治的入门题目 我们知道,二维度的偏序问题直接通过,树状数组就可以实现了,但是三维如何实现呢? 我记得以前了解过一个小故事,应该就是分治的. 一个皇帝,想给部下分配任务,但是部下太多 ...
- CDQ分治入门 + 例题 Arnooks's Defensive Line [Uva live 5871]
CDQ分治入门 简介 CDQ分治是一种特别的分治方法,它由CDQ(陈丹琦)神犇于09国家集训队作业中首次提出,因此得名.CDQ分治属于分治的一种.它一般只能处理非强制在线的问题,除此之外这个算法作为某 ...
- cdq分治入门学习 cogs 1752 Mokia nwerc 2015-2016 G 二维偏序
/* CDQ分治的对象是时间. 即对于一个时间段[L, R],我们取mid = (L + R) / 2. 分治的每层只考虑mid之前的修改对mid之后的查询的贡献,然后递归到[L,mid],(mid, ...
- cdq分治入门and持续学习orz
感觉cdq分治是一个很有趣的算法 能将很多需要套数据结构的题通过离线来做 目前的一些微小的理解 在一般情况下 就像求三维偏序xyz 就可以先对x排序 然后分治 1 cdq_x(L,M) ; 2 提取出 ...
- CDQ分治入门
前言 \(CDQ\)分治是一个神奇的算法. 它有着广泛的用途,甚至在某些题目中还能取代\(KD-Tree\).树套树等恶心的数据结构成为正解,而且常数还小得多. 不过它也有一定的缺点,如必须离线操作, ...
- caioj1097: [视频]树状数组1(快速求和计算) cdq分治入门
这题虽然是个树状数组,但是也可以用cdq分治做啊~~,这个就是一个浅显的二维偏序的应用? cdq分治和普通的分治有什么区别? 举个栗子:有4个小朋友,你请他们吃饭,假如你分治搞,就会分成很多子问题—— ...
- bzoj3262陌上花开 cdq分治入门题
Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),又三个整数表示.现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量.定义一朵花A比另一朵花B要美丽,当 ...
随机推荐
- 2021-11-29 Wpf的ViewModel和xaml绑定
ViewModel代码 public class MainViewModel { MainWindow _mainWindow; public MainViewModel(MainWindow mai ...
- 代码随想录算法训练营第三天| LeetCode 242.有效的字母异位词 349. 两个数组的交集 1. 两数之和
242.有效的字母异位词 卡哥建议: 这道题目,大家可以感受到数组用来做哈希表给我们带来的遍历之处. 题目链接/文章讲解/视频讲解: https://programmercarl.com ...
- Sealos 国内集群正式上线,可一键运行 LLama2 中文版大模型!
2023 年 7 月 19 日,MetaAI 宣布开源旗下的 LLama2 大模型,Meta 首席科学家.图灵奖得主 Yann LeCun 在推特上表示 Meta 此举可能将改变大模型行业的竞争格局. ...
- [语音识别] 基于Python构建简易的音频录制与语音识别应用
语音识别技术的快速发展为实现更多智能化应用提供了无限可能.本文旨在介绍一个基于Python实现的简易音频录制与语音识别应用.文章简要介绍相关技术的应用,重点放在音频录制方面,而语音识别则关注于调用相关 ...
- Jmeter MD5加密及其运用
常用的几种加密方式 内置函数__MD5加密 参数说明: String to calculate MD5 hash(必填):要加密的字符串 Name of variable in which to st ...
- Gradle安装配置教程
一.安装前检查 检查电脑上是否安装JDK,如果没有安装,请查看JDK安装教程:点击查看 如果电脑上已经安装JDK,按Win + R键,输入cmd,然后点击确定 输入java -version,点击回车 ...
- 使用C#发送邮件支持 Implicit SSL
安装Package: Install-Package AIM 使用下面的代码发送: class Mail { private static string mailAddress = "{yo ...
- PYQT5学习(12)Qtabwidget 选项卡及其窗口,Qstackedwidget和Qtabwidget的效果类似,以及系统托盘QsystemtrayIcon
参考博文:https://blog.csdn.net/jia666666/article/details/81669092QTabWidget控件提供一个选项卡和一个页面区域,默认显示第一个选项卡的页 ...
- pandas常用的数据类型,(serises和dataform)
- Ubuntu 18.04安装RabbitMQ
1.安装erlang语言环境 sudo apt install erlang-nox 2.更新Ubuntu 源 sudo apt update 3.安装RabbitMQ服务 sudo apt inst ...