CDQ 分治
引言:
什么是CDQ分治?其实这是一种思想而不是具体算法,因此CDQ分治覆盖的范围相当广泛,在 OI 界初见于陈丹琦 2008 年的集训队作业中,故被称为CDQ分治。
大致分为三类:
- cdq分治解决与点对有关的问题
- cdq分治优化1D/1D 动态规划的转移
- 通过 cdq 分治,将一些动态问题转化为静态问题
先总体说一下CDQ分治:
通常用来解决一类“修改独立,允许离线”的数据结构题。实际上它的本质是按时间分治,即若要处理时间\([l,r]\)上的修改与询问操作,就先处理\([l,mid]\)上的修改对\([mid + 1,r]\)上的询问的影响,之后再递归处理\([l,mid]\)与\([mid + 1,r]\),根据问题的不同,这几个步骤的顺序有时也会不一样。
CDQ 分治会使得我们考虑的问题的思维难度与代码难度大大减小,通常利用 CDQ 分治能使得一个树套树实现的题目,能够去掉外层的树,改为用分治来进行求解。
算法描述
CDQ分治适用于满足一下两个条件的数据结构题:
- 修改操作对询问的贡献独立,修改操作之间互不影响效果
- 题目允许使用离线算法
我们不妨假设我们需要(按顺序)完成的操作序列称为S,考虑将整个操作序列等分为前后两个部分,那么我们可以发下以下两个性质:
- 显然,后一半操作序列中的修改操作对前一半操作序列中的询问结果不会产生任何影响。
- 后一半操作序列中的询问操作只受两方面影响:一是前一半操作序列中的所有修改操作;二是后一半操作序列中,在该询问操作之前的修改操作。
容易发现,因为后一半操作序列的修改操作完全不会影响前一半操作序列中的询问结果,因此前一半操作序列的查询实际是与后一半操作序列完全独立的,是与原问题完全相同的子问题,可以递归处理。
接下来我们来考虑后一半操作序列中的询问操作。我们发现,影响后一半操作序列询问的答案的因素中,第二部分“后一半操作序列中,在该询问操作之前的修改操作”也是与前一半序列完全无关的(因为我们前面已经假定题目中的修改操作互相独立互不影响,而询问操作更不会影响修改操作了)因此,这部分因素也是与原问题完全相同的完全独立的子问题,可以递归处理。
我们还可以发现,影响后一半操作序列询问答案案的因素的第一部分“前一半操作序列中的所有修改操作”虽然与前一半序列密切相关,但它有一个非常好的性质,就是现在后一半操作序列的询问都是在前一半操作序列的所有修改完成之后执行的,那么对于影响后半部分询问的前半部分修改,他们对于任意一个后半部分询问的影响都是一模一样的。这时,原问题的动态修改操作便不再存在了,而被转化为了离线的、与原问题同样规模的“一开始给出所有修改”然后“回答若干询问”的更简单的问题,从而简化算法。
不妨设“解决无动态修改操作的原问题”的复杂度为\(O(f(n))\),那么由主定理,我们知道这样分治的时间复杂度是\(O(f(n)logn)\)
因此,只要数据结构题满足我们上文假定的两个要求:修改独立,允许离线,就可以以一个\(log\)的代价,将原问题中的动态修改去掉,变为没有动态修改的简化版问题,极大简化我们的思维与代码难度。
这种对时间分治的方法要求操作之间是互相独立的,因此如果有形如“撤销某次操作”这样的操作,就不能直接按上面的方法来解决问题,因为现在的插入操作可能会被后面的删除操作撤销掉,修改操作并不是完全独立的。
我们依然把操作序列等分为前后凉拌。我们考虑前一半操作中的询问操作,它们的答案显然与后半部分的插入与删除操作无关,因此他们仍然可以直接递归。
考虑后一半操作中的询问操作,它们的答案只与两部分内容有关:
- 前一半操作序列中的所有插入操作中在该询问之前未被删除的部分。
- 后一半操作序列中,在该询问操作之前的且未被删除的插入操作。
我们发现,因为后一半序列中的插入操作显然不会在前一半序列中被删除,因此“后一半操作序列中,在该询问操作之前的且未被删除的插入操作“这一部分与前一半操作序列完全无关,也就是与原问题相同的子问题,可以递归求解。
因此现在我们实际要处理的内容是:
“前一半操作序列中的所有插入操作中未被删除的部分”对“后一半操作序列的询问操作”的贡献。
因此,我们的实际任务是初始时给定一些插入操作,然后现在有个操作序列,每个操作会是删除一个插入操作,或是一个询问操作。
这个问题直接解决还是非常困难,但我们发现现在问题中只有删除没有插入,于是一个经典的解决办法就可以派上用场了,那就是“时光倒流”。
因为我们使用的是离线算法,我们完全可以预知转化后的问题中“初始的插入操作”,“改定的删除操作与询问操作”等所有需要的信息,那么我们能实现求出到最后都没有被删除掉的那些初始的插入操作,接着我们逆序处理操作序列,这样询问操作没有变,而删除操作变为了插入操作。利用时光倒流,我们再次把文义转化为了与之前相同的问题。
现在,只要插入操作贡献独立,插入操作之间互不影响,且题目允许离线,即使有身处或者变更操作(变更操作实际等价于西安深处源操作,再插入新操作),我们也可以利用CDQ分治与时光倒流,以2个log的时间复杂度为代价,把题目假话为没有动态插入,没有动态身处,没有动态变更的完全静态版问题。
按照开篇说的三类问题来具体分析的话
CDQ分治解决与点对有关的问题
这类问题一般是给你一个长度为\(n\)的序列,然后当你统计有一些特性的点对\((i,j)\)有多少个,又或者说是找到一对点\((i,j)\)使得一些函数的最大之类的问题
那么CDQ分治机遇这样一个算法流程解决这类问题:
找到这个序列的重点\(mid\)
讲所有点对\((i,j)\)划分为3类
- 第一种是$ 1 \leq i \leq mid,1 \leq j \leq mid$的点对
- 第二种是\(1 \leq i \leq mid,mid +1 \leq j \leq n\)的点对
- 第三种是\(mid +1 \leq i \leq n,mid + 1 \leq j \leq n\)
讲\((1,n)\)这个序列祭城两个序列\((1,mid)\)和\((mid + 1,n)\)会发现第一类点对和第三类点对都在这两个序列之中,递归的去解决这两类点对
想方设法处理一下第二类点对的信息
实际应用时候我们通常都是写一个函数\(solve(l,r)\)表示我们在处理\(l \leq i \leq r,l \leq j \leq r\)的点对
所以刚才的算法流程中的递归部分我们就是通过\(solve(l,mid),solve(mid,r)\)来实现的
所以说cdq分治知识一种十分模糊的思想,可以看大这种思想就是不断的把点对通多递归的方式分给左右两个区间
至于我们设计出来的算法真正干活的部分就是第四部分需要我们想方设法解决的部分
所以我们上几道例题看一下第四部分一般该怎么写
比如说我们来一个cdq分治的经典问题——三维偏序
三维偏序
给定一个序列,每个点有两个属性\((a,b)\),试求:这个序列里有多少对点对\((i,j)\)满足\(i < j,a_i < a_j,b_i<b_j\)
统计序列里点对的个数?我们套一个cdq
假设我们现在正在\(solve(l,r)\)并且通过某些奥妙重重的手段搞定了\(solve(l,mid)\)和\(solve(mid + 1,r)\)
那么我么现在就是统计满足\(l \leq i \leq mid,mid +1 \leq j \leq r\)的点对\((i,j)\)中,有多少个点对还满足\(i < j,a_i < a_j,b_i < b_j\)的限制条件
然后可以发现\(i < j\)的限制条件没有用,既然\(i\)比\(mid\)小,\(j\)比\(mid\)大,那\(i\)一定比\(j\)小_
还可以发现还剩下两个限制条件\(a_i<a_j,b_i<b_j\),根据这个限制条件我们就可以枚举\(j\),求出有多少个满足条件的\(i\)
为了方便枚举,我们把\((l,mid)\)和\((mid + 1,r)\)中的点全部按照\(a\)值从小到大排个序,之后我们依次枚举每一个\(j\),把所有\(a_i<a_j\)的点\(i\)全部插入一个数据结构里。此时我们只要对这个数据结构进行询问:里面有多少个点的\(b\)值是小雨\(b_j\)的,我们就对于这个点\(j\)求出了有多少个\(i\)可以和他合法的匹配了
这个数据结构用树状数组就可了
当我们插入一个\(b\)值等于\(x\)的点时,我们就令树状数组\(x\)这个位置单点+1,而查询数据结构里有多少个点小于\(x\)的操作实际上就是在求前缀和,只要我们实现对于所有的\(b\)值做了离散化我们的复杂度就是对的
文义又来了,钓鱼台有每一个\(j\)我们都需要将所有\(a_i < a_j\)的点插入树状数组中,这样的话我们总共要对树状数组做\(O(n^2)\)次操作。
由于我们把所有的\(i\)和\(j\)都事先按照\(a\)值排好序了,我们以双指针的方式在树状数组里插入点,这样的话我们就只需要做\(O(n)\)次插入操作就行了
所以通过这样一个算法流程我们就用\(O(nlogn)\)的时间处理完了关于第2类点对的信息了
这样的话算法复杂度就是
\]
例题[CQOI2011]动态逆序对
和三维偏序差不多,差不多是一个板子题
#define B cout << "BreakPoint" << endl;
#define O(x) cout << #x << " " << x << endl;
#define O_(x) cout << #x << " " << x << " ";
#define Msz(x) cout << "Sizeof " << #x << " " << sizeof(x)/1024/1024 << " MB" << endl;
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#define LL long long
#define inf 1000000009
const int N = 1e5 + 3,M = 2e5 + 3;
using namespace std;
inline int read() {
int s = 0,w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-')
w = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * w;
}
int n,m,c[N],p;
struct treearray {
int ta[N];
void ub(int& x) { x += x & (-x); }
void db(int& x) { x -= x & (-x); }
void c(int x, int t) {
for (; x <= n + 1; ub(x)) ta[x] += t;
}
inline int sum(int x) {
int r = 0;
for (; x > 0; db(x)) r += ta[x];
return r;
}
} ta;
struct data {
int val,del,ans;
} a[N];
LL res;
bool cmp1(const data& a, const data& b) { return a.val < b.val; }
bool cmp2(const data& a, const data& b) { return a.del < b.del; }
void solve(int l, int r) {
if (r - l == 1) return;
int mid = (l + r) / 2;
solve(l, mid), solve(mid, r);
int i = l + 1,j = mid + 1;
while(i <= mid) {
while (a[i].val > a[j].val && j <= r) ta.c(a[j].del, 1),j++;
a[i].ans += ta.sum(m + 1) - ta.sum(a[i].del);
i++;
}
i = l + 1,j = mid + 1;
while(i <= mid) {
while(a[i].val > a[j].val && j <= r) ta.c(a[j].del, -1),j++;
i++;
}
i = mid,j = r;
while(j > mid) {
while(a[j].val < a[i].val && i > l) ta.c(a[i].del,1),i--;
a[j].ans += ta.sum(m + 1) - ta.sum(a[j].del);
j--;
}
i = mid,j = r;
while(j > mid) {
while(a[j].val < a[i].val && i > l) ta.c(a[i].del,-1),i--;
j--;
}
sort(a + l + 1, a + r + 1, cmp1);
return;
}
int main() {
n = read(),m = read();
for(int i = 1; i <= n; i++) a[i].val = read(),c[a[i].val] = i;
for(int i = 1; i <= m; i++) p = read(),a[c[p]].del = i;
for(int i = 1; i <= n; i++) if(a[i].del == 0) a[i].del = m + 1;
for(int i = 1; i <= n; i++) res += ta.sum(n + 1) - ta.sum(a[i].val),ta.c(a[i].val, 1);
for(int i = 1; i <= n; i++) ta.c(a[i].val, -1);
solve(0, n);
sort(a + 1, a + n + 1, cmp2);
for(int i = 1; i <= m; i++) printf("%lld\n", res),res -= a[i].ans;
return 0;
}
CDQ 分治的更多相关文章
- 【教程】简易CDQ分治教程&学习笔记
前言 辣鸡蒟蒻__stdcall终于会CDQ分治啦! CDQ分治是我们处理各类问题的重要武器.它的优势在于可以顶替复杂的高级数据结构,而且常数比较小:缺点在于必须离线操作. CDQ分治的基 ...
- BZOJ 2683 简单题 ——CDQ分治
[题目分析] 感觉CDQ分治和整体二分有着很本质的区别. 为什么还有许多人把他们放在一起,也许是因为代码很像吧. CDQ分治最重要的是加入了时间对答案的影响,x,y,t三个条件. 排序解决了x ,分治 ...
- HDU5618 & CDQ分治
Description: 三维数点 Solution: 第一道cdq分治...感觉还是很显然的虽然题目不能再傻逼了... Code: /*=============================== ...
- 初识CDQ分治
[BZOJ 1176:单点修改,查询子矩阵和]: 1176: [Balkan2007]Mokia Time Limit: 30 Sec Memory Limit: 162 MBSubmit: 200 ...
- HDU5322 Hope(DP + CDQ分治 + NTT)
题目 Source http://acm.hdu.edu.cn/showproblem.php?pid=5322 Description Hope is a good thing, which can ...
- BZOJ4170 极光(CDQ分治 或 树套树)
传送门 BZOJ上的题目没有题面-- [样例输入] 3 5 2 4 3 Query 2 2 Modify 1 3 Query 2 2 Modify 1 2 Query 1 1 [样例输出] 2 3 3 ...
- BZOJ2683 简单题(CDQ分治)
传送门 之前听别人说CDQ分治不难学,今天才知道果真如此.之前一直为自己想不到CDQ的方法二很不爽,今天终于是想出来了一道了,太弱-- cdq分治主要就是把整段区间分成两半,然后用左区间的值去更新右区 ...
- BNUOJ 51279[组队活动 Large](cdq分治+FFT)
传送门 大意:ACM校队一共有n名队员,从1到n标号,现在n名队员要组成若干支队伍,每支队伍至多有m名队员,求一共有多少种不同的组队方案.两个组队方案被视为不同的,当且仅当存在至少一名队员在两种方案中 ...
- 【BZOJ-3262】陌上花开 CDQ分治(3维偏序)
3262: 陌上花开 Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 1439 Solved: 648[Submit][Status][Discuss ...
- 【BZOJ-1176&2683】Mokia&简单题 CDQ分治
1176: [Balkan2007]Mokia Time Limit: 30 Sec Memory Limit: 162 MBSubmit: 1854 Solved: 821[Submit][St ...
随机推荐
- Struts2学习-jsp中超链接传参问题
今天在学习过程中对struts2中超链接的传参问题产生了一些疑惑,不明白jsp中的超链接如何将参数传到Action方法中去的. <s:iterator value="categorys ...
- [POI2006] SZK-Schools - 费用流
差不多就是个二分图带权匹配?(我还是敲费用流吧) 每个点向着自己能到的学校连边,费用按题意设定 跑最小费用最大流即可 #include <bits/stdc++.h> using name ...
- Wannafly Camp 2020 Day 6M 自闭 - 模拟
按题意模拟,又乱又烦,没什么可说的 #include <bits/stdc++.h> using namespace std; #define int long long int n,m, ...
- Spring Data JPA介绍与简单案例
一.Spring Data JPA介绍 可以理解为JPA规范的再次封装抽象,底层还是使用了Hibernate的JPA技术实现,引用JPQL(Java Persistence Query Languag ...
- wget安装nginx
#下载: wget http://nginx.org/download/nginx-1.8.0.tar.gz #解压: tar -zxvf nginx-1.8.0.tar.gz #安装依赖插件 yum ...
- python+selenium自动化禅道登录测试
本文以禅道登录测试为例,思路主要分openBrowser.openUrl.findElement.sendVals.checkResult.ReadUserdate六部分 openBrowser de ...
- 【动态规划】【C/C++】简单的背包问题
简单的背包问题 背包问题动态规划中非常经典的一个问题,本文只包含01背包,完全背包和多重背包.更加详尽的背包问题的讲解请参考崔添翼大神的<背包九讲> 简单的01背包 问题导入:新年到了,m ...
- JDBC未知列
Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4. MySQLSyntaxErrorException: Unk ...
- HTML下拉请求列表标签
select下拉请求列表 option选择项(内容) selected默认值
- 巨杉数据库入选年度Gartner Peer Insights报告,获得市场高度评价
Gartner Peer Insights 年度评选结果于近日出炉,在数据库管理系统市场报告中,巨杉数据库获得了总平均分4.7(满分5分)的成绩,在众多国际厂商中位居第三,是国内唯一一家入选的数据库厂 ...