一篇自己都看不懂的CDQ分治&整体二分学习笔记

本文深入解析CDQ分治与整体二分算法,介绍其基本思想、应用场景及实现细节,通过典型例题展示算法优势,适用于高时间与空间效率需求的离线问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作为一个永不咕咕咕的博主,我来更笔记辣qaq


CDQ分治

CDQ分治的思想还是比较简单的。它的基本流程是:

\(1.\)将所有修改操作和查询操作按照时间顺序并在一起,形成一段序列。显然,会影响查询操作结果的修改操作在序列中一定会在这一个查询操作前面

\(2.\)将这一段序列分为左右两半,递归解决左右两半的子问题

\(3.\)考虑左半部分的修改操作对右半部分的查询操作的贡献

CDQ分治的基本思想就是在分治的过程中统计左半部分对右半部分的影响

上面的过程可能比较抽象,举个栗子:归并排序求逆序对

别告诉我你只会树状数组求逆序对

回顾一下归并排序求逆序对的过程:

\(1.\)将当前待排序的序列分为左右两个区间,计算左右区间内部的逆序对数量并将左右区间分别排好序

\(2.\)通过双指针计算一个数在左区间、另一个数在右区间的逆序对数量并将原序列排好序

上面加粗的部分与CDQ分治是异曲同工的。

还有些模糊其实是很正常的,下面来看几道例题感受一下:

三维偏序

二维偏序就是逆序对统计,那么三维偏序应该怎么做呢?我们设三个维度为\((a,b,c)\),那么方法如下:

\(1.\)对所有点的\(a\)维排序

\(2.\)\(b\)维归并排序

然后我们考虑在分治过程中如何统计左半边区间对右半边区间的贡献。

可以发现,在当前分治区间合并\(b\)维的时候,\(a\)维是无序的,但是一定会有左半区间的点的\(a\)小于右半区间的点的\(a\),所以我们不需要考虑\(a\),只需要统计对于每一个右半区间的点,有多少个\(b,c\)维同时小于它即可。

这个可以在对\(b\)维进行归并排序的时候使用树状数组对\(c\)维进行维护。

然后就做完了QuQ复杂度\(O(nlog^2n)\)

注意一些小细节:

①在合并左右两个区间时可以直接快速排序,因为分治+树状数组已经有\(log^2n\)的复杂度了,快排不会影响最后的复杂度,而且似乎比直接归并要快一些

②每一次树状数组清空的时候不要暴力\(memset\)(这样子复杂度就会退化为\(O(n^2)\)),应该是将左半区间的贡献在树状数组中清除,这样才能保证复杂度。

当然了以上的方法是可以拓展到多维的(使用CDQ+CDQ+CDQ+...+树状数组解决),但是一般当维度大于\(4\)维时使用\(bitset\)会有更优的复杂度。

这里是代码菌

动态逆序对

考虑每一次删除实际上会减掉什么

首先考虑会减掉这一个数组成的逆序对数量,然后能够发现会减多一部分。

减多了什么呢?如果两个数能够产生逆序对,那么在这两个数都被删除的时候,这一个逆序对会被算两次。

那么我们考虑在后面被删除的数被删除的时候将这一个贡献加回来

对于每一次删除操作,设其为\((time,pos,val)\),那么我们需要对于每一个\(j\)求满足\(time_i<time_j,pos_i>pos_j,val_i<val_j\)或者\(time_i<time_j,pos_i<pos_j,val_i>val_j\)\(i\)的数量。可以发现这是一个三维偏序,然后直接上板子即可。

代码

NOI2007 货币兑换

这一道题是CDQ论文里面的题目

做这道题你需要的前置芝士是斜率优化,顺便给我的斜率优化笔记打个广告QAQ

我们来愉快地推\(DP\)

\(f_i\)表示在第\(i\)天能够获得的最大收益,设\(g_i\)为第\(i\)天最多能够兑换的\(B\)券数量

那么有\(g_i \times B_i + g_i \times Rate_i \times A_i = f_i\),即\(g_i = \frac{f_i}{B_i + Rate_i \times A_i}\)

而我们的\(DP\)转移为:\(f_i = \max\{A_i\max\limits_{j=1}^{i-1}\{g_j\frac{B_i}{A_i} + g_jRate_j\},f_{i-1}\}\)

可以发现我们的转移是一个斜率式子,可以使用斜率优化解决。但是\(g_i\)\(i\)并不呈正相关,意味着我们不能像传统的斜率优化一样使用单调队列解决凸包的维护问题

这个时候有两种做法:使用平衡树维护凸包和使用分治维护凸包。第一种不是这篇文章的重点,所以我们直接看第二种

考虑当前正在求\([l,r]\)\(f\)值,首先我们先递归进入左区间,将左区间的\(f\)\(g\)的值求出来,然后先在当前层处理左区间对右区间的影响,然后递归进入右区间求解

对于当前区间的贡献的计算,首先将左区间的所有直线按照斜率排序,使用单调栈建立凸包,然后将右区间的所有询问按照\(x\)递增排序,因为\(x\)递增,所以可以直接在凸包上扫一遍得到答案。当然这一部分也可以不排序,通过在凸包上二分解决。

注意到上面的递归顺序与我们之前见到的大部分分治的递归顺序是不同的,这是因为:如果我们需要正确地得到\(f_i\),意味着对于所有的\(j<i\)\(f_j\)必须要在\(f_i\)之前算好并将贡献给\(f_i\)\(f_i\)才能够正确地给其他值进行贡献。如果我们在当前区间计算贡献之前计算右区间,意味着右区间的所有\(f_i\)都在没有正确地计算出值得情况下对其他值进行了贡献,这会导致答案的错误。

代码

练习题:BZOJ4237 稻草人

题解在这里

整体二分

整体二分与CDQ分治的做法有些像

整体二分的基本思路是将一堆操作和询问放在一起进行二分,一般可以做到\(O(nlog^2n)\)的复杂度

假设我们现在正在解决\(solve(ql,qr,l,r)\),这表示我们正在解决\([ql,qr]\)区间内的操作和询问,而且满足:区间内所有修改操作的权值范围在\([l,r]\)内,并且已经确定区间内所有询问的答案在\([l,r]\)区间内

接下来是整体二分最重要的\(check\)部分:

\(mid=\frac{l+r}{2}\),从左往右执行所有修改和查询操作:

如果某个修改操作的权值范围在\([l,mid]\)内,执行它,否则忽略掉它

如果到了一个查询操作,进行查询,如果满足答案在\([l,mid]\)内的条件,那么它的答案就在\([l,mid]\)范围内,否则在\([mid+1,r]\)范围内

最后把所有权值范围在\([l,mid]\)的修改操作和答案在\([l,mid]\)的查询放在一起,其他的修改和查询放在一起,两边分别递归求解。递归边界为\(l=r\),就可以确定\([ql,qr]\)的所有询问的答案为\(l\)

当然,可能在实际的\(check\)过程中需要执行的是\([mid+1,r]\)部分的修改操作。

可能理解起来比较抽象(分治理解起来都比较抽象)来看两道题感受一下:

ZJOI2010 K大数查询

这道题曾经树套树时空双卡,但整体二分表示很轻松;现在空间开大了,但是树套树还是因为超大的常数容易被卡……

考虑\(solve\)函数的\(check\)过程,不妨这么做:

使用一个权值线段树维护每一个位置中,数字范围在\([mid+1,r]\)的数的个数。

对于在\([mid+1,r]\)范围内的修改,也就是加入的数在\([mid+1,r]\)范围内的操作,在它对应的操作区间上\(+1\)

对于一个查询操作,如果这个查询操作对应的区间上数字范围在\([mid+1,r]\)的数的个数大于等于这一次询问的\(K\),则表示这一个询问的答案在\([mid+1,r]\)范围内,否则在\([l,mid]\)范围内。

值得注意的是,要清除\([mid+1,r]\)的修改操作对答案在\([l,mid]\)范围内的询问的贡献。

代码

HNOI2016 网络

这道题数据比较水,以至于树链剖分+线段树+堆的\(O(nlog^3n)\)算法都怼过去了

仍然考虑\(check\)函数,我们只对权值在\([mid+1,r]\)的范围内的修改进行操作,操作的方式考虑差分:预处理树的\(dfs\)序,对于一个加入操作\(a,b\),在\(dfn_a,dfn_b\)\(+1\),然后在\(dfn_{LCA(a,b)},dfn_{fa_{LCA(a,b)}}\)\(-1\),删除操作反过来即可。

对于一个查询操作,我们需要查询是否当前加入的所有链都经过了当前点,我们直接查询当前点对应的子树中的权值和,如果它与当前加入的链数相等就表示所有链都经过了,答案在\([l,mid]\)范围内,否则在\([mid+1,r]\)范围内。正确性显然分类讨论一下就行了

代码来这里看啦啦啦

练习题:POI2011 Meteors

题解在这里

总结

CDQ分治与整体二分是两种比较优秀的分治算法,一般适用于以下情形:

\(1.\)不强制在线

\(2.\)对时间限制和空间限制要求比较高,使用高级数据结构(点名树套树!)容易被卡

虽然离线是这两种分治算法最大的软肋,出题人只需要加上异或\(lastans\)分治就GG了,但仍然不能改变这两种分治算法的重要地位。

转载于:https://www.cnblogs.com/Itst/p/10224038.html

&lt;think&gt;首先,用户的问题是关于CDQ分治算法基本原理及其应用场景。我需要根据系统级指令来构建我的回答。 系统级指令关键点: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 参考站内引用: - 引用[1]:关于分治思想的描述,包括分块、递归等。 - 引用[2]:关于书籍的引用,不相关。 - 引用[3]:关于下载资源的链接,不相关。 用户真正的需求是理解CDQ分治的概念和原理,所以我需要专注于CDQ分治CDQ分治基本原理: - CDQ分治是由陈丹琦(Chen Danqi)提出的一种算法,用于处理偏序问题,如三维偏序问题。 - 它是分治思想的一种应用,特别适合解决离线查询问题。 - 核心思想:将问题分成子问题,递归解决,然后合并结果,处理跨越子问题的部分。 应用场景: - 三维偏序问题:例如,给定点集,统计每个点满足某些条件的点数。 - 动态规划优化:如斜率优化等。 - 其他离线查询问题。 我的回答结构: 1. **介绍CDQ分治**:简要说明其背景和目的。 2. **基本原理**:解释算法步骤,使用数学表达式描述。 3. **应用场景**:列出常见应用。 4. **生成相关问题**:在最后添加相关问题。 5. **添加引用**:在引用段落末尾自然添加引用标识。 数学表达式: - 行内数学:如变量、点坐标等,使用$...$。 - 独立公式:如方程,使用$$...$$。 例如: - 行内:点$P_i = (a_i, b_i, c_i)$ - 独立:偏序关系公式。 生成相关问题: - 基于CDQ分治,如&quot;CDQ分治如何优化三维偏序问题?&quot;或&quot;CDQ分治的时间复杂度分析?&quot; 确保回答真实可靠: - 基于算法知识,CDQ分治常用于OI竞赛中处理偏序问题。 引用处理: - 参考引用[1]是关于分治思想的,我可以引用它来支持分治部分。 - 其他引用不相关,但系统说&quot;请尽可能多的参考用户上一次的问题和你的回答&quot;,但用户没有上一次问题,所以基于当前引用。 最终回答大纲: - 开头:介绍CDQ分治。 - 原理部分:分步骤解释,使用LaTeX。 - 应用场景:列举例子。 - 相关问题:在最后添加。 - 结尾:自然结束。&lt;/think&gt;### CDQ分治算法基本原理及其应用场景 CDQ分治(Chen Danqi Divide and Conquer)是由中国计算机科学家陈丹琦提出的一种分治算法变体,主要用于处理离线查询问题,特别是多维偏序问题(如三维偏序)。它在算法竞赛和数据处理中应用广泛,能高效解决一些复杂问题。下面我将逐步解释其基本原理和应用场景,确保内容清晰可靠。 #### 1. CDQ分治基本原理 CDQ分治基于经典分治思想,但针对偏序问题进行了优化。核心思路是将问题递归分解为子问题,处理子问题内部和子问题之间的影响,最后合并结果。算法主要分为三个步骤:分、治、合。以下是详细解释: - **分(Divide)**:将输入数据(如点集)按某一维度(通常是时间或索引)分成两个大致相等的子集,记为左子集$L$和右子集$R$。例如,给定点集$P = \{P_1, P_2, \dots, P_n\}$,其中每个点$P_i = (a_i, b_i, c_i)$表示三维坐标,我们按第一维$a_i$排序并分割: $$ \text{mid} = \left\lfloor \frac{n}{2} \right\rfloor $$ 这样得到$L = \{P_i \mid i \leq \text{mid}\}$和$R = \{P_i \mid i &gt; \text{mid}\}$。递归地对$L$和$R$应用相同过程。 - **治(Conquer)**:递归处理左子集$L$和右子集$R$,解决子问题内部的偏序关系。例如,在三维偏序问题中,目标是统计每个点$P_i$满足$a_j \leq a_i, b_j \leq b_i, c_j \leq c_i$的点数$j$($j \neq i$)。递归调用确保子集内部的问题被解决。 - **合(Merge)**:合并子问题结果,并处理跨越子集$L$和$R$的影响。这是CDQ分治的关键: - 使用辅助数据结构(如树状数组或线段树)高效处理跨子集贡献。 - 例如,在三维偏序中,合并时需统计$L$中的点对$R$中点的贡献。具体做法: 1. 对$L$和$R$按第二维$b_i$排序。 2. 遍历$R$中的每个点$P_j$,同时维护一个树状数组,用于记录第三维$c_i$的累积值。 3. 对于$P_j$,查询树状数组中满足$c_k \leq c_j$的点数($k$来自$L$),并累加到结果。 这确保了跨子集贡献在$O(n \log n)$时间内完成。 整个算法的时间复杂度为$O(n \log^2 n)$,其中$n$是数据规模。递归公式可表示为: $$ T(n) = 2T\left(\frac{n}{2}\right) + O(n \log n) $$ 其中$O(n \log n)$来自合并步骤的排序和树状数组操作。CDQ分治通过&ldquo;分而治之&rdquo;高效处理了偏序关系[^1]。 #### 2. CDQ分治的应用场景 CDQ分治特别适合离线查询问题,其中数据点固定,查询可批量处理。常见应用包括: - **三维偏序问题**:如统计逆序对或点对关系。例如,在竞赛中,给定点集统计每个点&ldquo;支配&rdquo;的点数(即所有维度都小于等于该点的点)。CDQ分治能高效解决,时间复杂度优于暴力$O(n^2)$。 - **动态规划优化**:用于优化序列问题,如最长上升子序列(LIS)或斜率优化问题。通过分治处理状态转移,减少计算量。 - **离线查询处理**:在数据库或算法中,处理批量查询,如区间统计或范围查询。CDQ分治将查询排序后递归处理,避免实时开销。 - **其他领域**:在计算几何(如最近点对问题)和机器学习数据预处理中也有应用,但需结合具体算法调整。 CDQ分治的优势在于其通用性和效率,但要求问题具有&ldquo;可分治&rdquo;,即子问题独立且合并步骤可高效实现。实际应用中,常结合树状数组等数据结构提升性能。 #### 相关问题 1. CDQ分治如何处理更高维度的偏序问题(如四维偏序)? 2. CDQ分治普通分治算法的主要区别是什么? 3. CDQ分治在动态规划优化中的具体实现案例? [^1]: 分治思想的核心是递归分解问题,然后合并结果,这在CDQ分治中得到充分体现,如分割点集并递归处理子问题。 [^2]: 算法理论常参考经典书籍,但CDQ分治更常见于竞赛和算法论文。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值