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要美丽,当 ...
随机推荐
- <学习笔记> 关于错排列
1 是做排列计数的时候了解到这个东西: 一开始想的是用容斥原理,先加上全排列,再减去不满足的,再加上重复的,再减去不满足的...... 后来发现还涉及到杨辉三角,麻烦死了,时空复杂度也过不去,然后就知 ...
- linux基础:编译
程序编译 在linux中,gnu项目提供了gcc编译器.g++编译器和gdb调试器. C和C++语言正在不断发展,为了保持兼容程序语言的最新特性,开发者通常选择GCC来编译C语言编写的源代码,选择G+ ...
- 记一次 .NET某报关系统 非托管泄露分析
一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序内存会出现暴涨,让我看下是怎么事情?而且还告诉我是在 Linux 环境下,说实话在Linux上分析.NET程序难度会很大,难度大的原因在于Li ...
- 1.0 Python 标准输入与输出
python 是一种高级.面向对象.通用的编程语言,由Guido van Rossum发明,于1991年首次发布.python 的设计哲学强调代码的可读性和简洁性,同时也非常适合于大型项目的开发.py ...
- 质效提升 | QA不做业务需求测试,你怎么看?
因为有的小伙伴看到公司的QA不测试业务需求,只搞流程.卡点.规范.技术创新.QA平台,行业洞察,让研发自测.研发担责上线bug和风险,所以问我,你怎么看QA不做业务需求测试这件事.其实我怎么看不重要 ...
- Combobox后台绑定
本文主要介绍WPF中Combobox的后台绑定,我在这里主要讲解数据驱动 1.对于前台绑定,我们首先写出想要绑定的对象 新建一个Models文件夹,将Student类写入 public class S ...
- 浅谈基于QT的截图工具的设计与实现
本人一直在做属于自己的一款跨平台的截图软件(w4ngzhen/capi(github.com)),在软件编写的过程中有一些心得体会,所以有了本文.其实这篇文章酝酿了很久,现在这款软件有了雏形,也有空梳 ...
- 快手根据关键词取商品列表 API 返回值说明
item_search-根据关键词取商品列表 注册开通 ks.item_search 公共参数 名称 类型 必须 描述 key String 是 调用key(必须以GET方式拼接在URL中) secr ...
- yum&二进制安装PostgreSQL 12
一.yum安装&配置PostgreSQL 12 目录 一.yum安装&配置PostgreSQL 12 一.前言 1.本文主要内容 2.本文环境信息与适用范围 二.PostgreSQL安 ...
- 领域驱动设计(DDD):DDD落地问题和一些解决方法
欢迎继续关注本系列文章,下面我们继续讲解下DDD在实战落地时候,会具体碰到哪些问题,以及解决的方式有哪些. DDD 是一种思想,主要知道我们方向,具体如何做,需要我们根据业务场景具体问题具体分析. 充 ...