cdq(陈丹琦)分治,是一种类似二分的算法。基本思想同分治:

  1. 递归,把大问题划分成若干个结构相同的子问题,直到(L==R);
  2. 处理左区间[L,mid]对右区间[mid+1,R]的影响;
  3. 合并。

它可以顶替复杂的高级数据结构,但必须离线操作。

N维偏序,就是求N个关键字下的顺/逆序对。cdq分治是这类题中常用的降维手段。

一维偏序

  学习归并排序时,我们了解到它的一个特性就是可以用来求逆序对。

  Luogu P1908 逆序对

void merge(int L,int R) {
if(L == R)return;
int mid = (L+R)/;
merge(L,mid);
merge(mid+,R);
int idx = L;
int i = L,j = mid+;
while(i <= mid&&j <= R) {
if(a[i] <= a[j])temp[idx++] = a[i++];
else {
temp[idx++] = a[j++];
cnt += mid-i+;
}
}
while(i <= mid)temp[idx++] = a[i++];
while(j <= R)temp[idx++] = a[j++];
for(int i = L; i <= R; i++)
a[i] = temp[i];
}

  归并排序求逆序对  

考虑它的原理:只统计对于右面的每一个元素,左边比它大的。

两边的数列都为有序,且各自的逆序对都已经统计完了。

那么对于右边的第j个元素(j>=mid+1),如果左边的第i个元素比j大,那么i+1,i+2....到mid一定都比j大。

这里就体现了cdq分治的思想,也是多维偏序的基础。可以说,归并排序求逆序对是cdq分治的一个特例。

二维偏序

  除了归并排序,一维偏序也可以用树状数组解决。实际上,一部分树状数组能解决的问题,cdq分治也可以解决。

  Luogu P3374 【模板】树状数组 1

#include<iostream>
#include<cstdio>
#include<cstring>
#define MogeKo qwq
using namespace std;
const int maxn = ;
int n,m,opt,x,y,sum[maxn]; int lowbit(int x){
return x & -x;
} void update(int x,int k){
while(x <= n){
sum[x] += k;
x += lowbit(x);
}
} int query(int x){
int ans = ;
while(x){
ans += sum[x];
x -= lowbit(x);
}
return ans;
} int main(){
scanf("%d%d",&n,&m);
for(int i = ;i <= n;i++){
scanf("%d",&y);
update(i,y);
}
for(int i = ;i <= m;i++){
scanf("%d%d%d",&opt,&x,&y);
if(opt == )update(x,y);
if(opt == )printf("%d\n",query(y)-query(x-));
}
return ;
}

  树状数组  

树状数组板子题,可以轻松解决。

把它转化为二维偏序问题,对于每个修改和询问,都有(时间,位置)两个维度。

开一个结构体q[],数组下标记录时间,q[].id记录位置,q[].type记录类型(修改或询问)。注意,当修改和询问在同一位置时,修改操作要优先。

解决二维偏序问题首先需要控制一维有序,另一维进行归并排序。在这里,时间默认就是有序的(++cnt);

对于每个修改操作,记录修改的元素位置。数组赋初值的方式和修改操作相同,可以当做时间在最前的修改。

查询怎么办?用树状数组求一段区间和时,需要用到前缀和,即 R-(L-1)。

那么,询问的位置也可以拆分成两个:(L-1)和 R。用不同的type来区分它们:( L-1的要减去,R的要加上)。

如何进行归并排序?对于一段位置有序的区间,一定是时间在前的修改操作会影响时间在后的查询操作。

用sum维护区间内修改操作的值,修改时用sum+修改值;

用ans记录询问的答案,ans -所有(L-1)的sum +所有R的sum 即为这个询问的结果。为啥非要用cdq分治啊麻烦死了QAQ!!!

#include<iostream>
#include<cstdio>
#include<cstring>
#define MogeKo qwq
using namespace std;
const int maxn = *; int n,m,cnt,cqry,opt,x,y,ans[maxn];
struct node{
int type,id,val;
bool operator < (const node & x) const{
if(id != x.id)return id < x.id;
else return type < x.type;
}
}q[maxn],tem[maxn]; void cdq(int L,int R){
if(L == R) return;
int mid = L+R>>;
cdq(L,mid),cdq(mid+,R);
int t1 = L,t2 = mid+;
int sum = ;
for(int i = L;i <= R;i++){
if( (t1 <= mid && q[t1]<q[t2]) || t2 > R){
if(q[t1].type == ) sum += q[t1].val;
tem[i] = q[t1++];
}
else{
if(q[t2].type == ) ans[q[t2].val] -= sum;
if(q[t2].type == ) ans[q[t2].val] += sum;
tem[i] = q[t2++];
}
}
for(int i = L;i <= R;i++) q[i] = tem[i];
} int main(){
scanf("%d%d",&n,&m);
for(int i = ;i <= n;i++){
cnt++;
scanf("%d",&y);
q[cnt].type = ;
q[cnt].id = i;
q[cnt].val = y;
}
for(int i = ;i <= m;i++){
scanf("%d%d%d",&opt,&x,&y);
if(opt == ){
q[++cnt].type = ;
q[cnt].id = x;
q[cnt].val = y;
}
if(opt == ){
cqry++;
q[++cnt].type = ;
q[cnt].id = x-;
q[cnt].val = cqry;
q[++cnt].type = ;
q[cnt].id = y;
q[cnt].val = cqry;
}
}
cdq(,cnt);
for(int i = ;i <= cqry;i++)
printf("%d\n",ans[i]);
return ;
}

  二维偏序  

三维偏序

  Luogu P3810 【模板】三维偏序(陌上花开)

扩展到三维。设三维分别为x,y,z

先按x排序,消除第一维的影响。

考虑不使用cdq,用一个树状数组维护第二维,另一个树状数组维护第三维...就会出现树套树的神奇情况

模仿之前的做法,第二维使用cdq分治,按y进行归并排序。虽然x的顺序被打乱了,但左一半一定小于右一半。第二维的影响被消除了。

第三维可以用一个权值树状数组维护。

int t1=L, t2=mid+;
while(t2 <= R){
while(t1 <= mid && b[t1].y <= b[t2].y){
tree.update(b[t1].z,b[t1].num);
t1++;
}
b[t2].ans += tree.query(b[t2].z);
t2++;
}

已经控制x2>x1,将所有y1<y2时按z1把当前花的个数加入树状数组,再查询比z2小的在树状数组中有多少个。

由于归并排序时,y2后的y3一定大于y1,所以已经加入的z的个数不用清空。

当归并的操作结束时,再把树状数组减去已经加入的左区间的z的个数(也就是左区间指针t1之前)。

提供的数据中,可能有xyz完全相同的情况,所以初始化时要先去重,但不能直接调用unique函数。统计相同的花的个数,用结构体的.num记录。

这样当把花按x加入树状数组时,加入.num中的个数就可以了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MogeKo qwq
using namespace std;
const int maxn = ;
int n,m,k,cnt[maxn]; struct node{
int x,y,z,num,ans;
bool operator < (const node & A) const {
return x<A.x || (x==A.x && (y<A.y || (y==A.y && z<A.z)));
}
bool operator == (const node & A) const {
return x==A.x && y==A.y && z==A.z;
}
}a[maxn],b[maxn]; bool cmpyz(node A,node B){
return A.y<B.y || (A.y==B.y && A.z<B.z);
} struct BIT{
int sum[maxn],len;
int lowbit(int x){
return x & -x;
}
void update(int x,int k){
for(int i = x; i<=len; i+=lowbit(i))
sum[i] += k;
}
int query(int x){
int ans = ;
for(int i = x; i; i-=lowbit(i))
ans += sum[i];
return ans;
}
}tree; void cdq(int L,int R){
if(L == R)return;
int mid = L+R>>;
cdq(L,mid),cdq(mid+,R);
sort(b+L,b+mid+,cmpyz);
sort(b+mid+,b+R+,cmpyz);
int t1=L, t2=mid+;
while(t2 <= R){
while(t1 <= mid && b[t1].y <= b[t2].y){
tree.update(b[t1].z,b[t1].num);
t1++;
}
b[t2].ans += tree.query(b[t2].z);
t2++;
}
for(int i = L;i <= t1-;i++)
tree.update(b[i].z,-b[i].num);
} int main(){
scanf("%d%d",&n,&k);
tree.len = k;
for(int i = ;i <= n;i++)
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
sort(a+,a+n+);
int bcnt = ;
for(int i = ;i <= n;i++){
bcnt++;
if(a[i]==a[i+])continue;
b[++m] = a[i], b[m].num = bcnt;
bcnt = ;
}
cdq(,m);
for(int i = ;i <= m;i++)
cnt[b[i].ans+b[i].num-] += b[i].num;
for(int i = ;i <= n-;i++)
printf("%d\n",cnt[i]);
return ;
}

  三维偏序  


其实cdq分治我也不是很明白qwq

理论上,cdq分治可以解决任意N维偏序问题。但是,cdq套cdq的复杂度会达到n logkn,当它超过n2的时候...还是选择暴力枚举吧w

N维偏序:cdq分治的更多相关文章

  1. Luogu 3810 & BZOJ 3262 陌上花开/三维偏序 | CDQ分治

    Luogu 3810 & BZOJ 3263 陌上花开/三维偏序 | CDQ分治 题面 \(n\)个元素,每个元素有三个值:\(a_i\), \(b_i\) 和 \(c_i\).定义一个元素的 ...

  2. COGS 2479. [HZOI 2016]偏序 [CDQ分治套CDQ分治 四维偏序]

    传送门 给定一个有n个元素的序列,元素编号为1~n,每个元素有三个属性a,b,c,求序列中满足i<j且ai<aj且bi<bj且ci<cj的数对(i,j)的个数. 对于100%的 ...

  3. [COGS2479 && COGS2639]高维偏序(CDQ分治,bitset)

    COGS2479:四维偏序. CDQ套CDQ CDQ:对a分治,对b排序,再对a打标记,然后执行CDQ2 CDQ2:对b分治,对c归并排序,对d树状数组. #include<cstdio> ...

  4. BZOJ3262 陌上花开 —— 三维偏序 CDQ分治

    题目链接:https://vjudge.net/problem/HYSBZ-3262 3262: 陌上花开 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit ...

  5. bzoj3262: 陌上花开 三维偏序cdq分治

    三维偏序裸题,cdq分治时,左侧的x一定比右侧x小,然后分别按y排序,对于左侧元素按y大小把z依次插入到树状数组里,其中维护每个左侧元素对右侧元素的贡献,在bit查询即可 /************* ...

  6. [bzoj] 3263 陌上花开 洛谷 P3810 三维偏序|| CDQ分治 && CDQ分治讲解

    原题 定义一个点比另一个点大为当且仅当这个点的三个值分别大于等于另一个点的三个值.每比一个点大就为加一等级,求每个等级的点的数量. 显然的三维偏序问题,CDQ的板子题. CDQ分治: CDQ分治是一种 ...

  7. BZOJ 3295:[Cqoi2011]动态逆序对(三维偏序 CDQ分治+树状数组)

    http://www.lydsy.com/JudgeOnline/problem.php?id=3295 题意:简单明了. 思路:终于好像有点明白CDQ分治处理三维偏序了.把删除操作看作是插入操作,那 ...

  8. 三维偏序[cdq分治学习笔记]

    三维偏序 就是让第一维有序 然后归并+树状数组求两维 cdq+cdq不会 告辞 #include <bits/stdc++.h> // #define int long long #def ...

  9. [偏序关系与CDQ分治]【学习笔记】

    组合数学真是太棒了 $CDQ$真是太棒了(雾 参考资料: 1.<组合数学> 2.论文 课件 很容易查到 3.sro __stdcall 偏序关系 关系: 集合$X$上的关系是$X$与$X$ ...

随机推荐

  1. How To Do Master Record Mass Maintenance

    How To Do Master Record Mass Maintenance Which master records mass maintenance can be done? What do ...

  2. Python 利用Python操作excel表格之openyxl介绍Part1

    利用Python操作excel表格之openyxl介绍 by:授客 QQ:1033553122 欢迎加入全国软件测试交流qq群(群号:7156436),免费获取以下性能监控工具(类似Nmon精简版) ...

  3. git 入门教程之回到过去

    回到过去 背景 现在你已经掌握git的基本操作了,文件发生更改首先使用 git add 添加更改,然后 git commit 提交全部更改,当本地文件再次发生更改时,仍然需要git add 和 git ...

  4. TURN Server Windows 安装程序

    有了OfficeSIP TURN Server 安装包,记录一下. http://www.onlinedown.net/soft/94746.htm 开源代码(C#)和应用地址:https://sou ...

  5. 使用Visual Studio Team Services敏捷规划和项目组合管理(二)——VSTS中的工作项

    使用Visual Studio Team Services敏捷规划和项目组合管理(二)--VSTS中的工作项 1.通过project/team下拉菜单选择MyHealthClinic\Web,导航到W ...

  6. MyBatis笔记----报错:Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/ij34/mybatis/applicationContext.xml]: Invocation of init method failed; nested exception is org.sp

    四月 05, 2017 4:51:02 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRef ...

  7. 如何用Jupyter notebook打开本地数据集

    首先打开本地Jupyter notebook,出现类似页面并进入网页端Home. 网页端界面类似如下: 需要注意的是,Jupyter notebook只能打开当前目录下的数据集,如csv,所以需要使用 ...

  8. LeetCode算法题-Fizz Buzz(Java实现)

    这是悦乐书的第221次更新,第233篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第88题(顺位题号是412). 编写一个程序,输出从1到n的数字的字符串表示.但对于三的 ...

  9. Servlet(二):初识Servlet

    在手动写完一个Servlet小例子后,是不是有很多疑问,接下来会为大家详细介绍Servlet的知识. 1.什么是Servlet 是在服务器上运行的小程序.一个servlet就是一个Java类,并且可以 ...

  10. Unity基础6 Shadow Map 阴影实现

    这篇实现来的有点墨迹,前前后后折腾零碎的时间折腾了半个月才才实现一个基本的shadow map流程,只能说是对原理理解更深刻一些,但离实际应用估计还需要做很多优化.这篇文章大致分析下shadow ma ...