先挂上个大佬讲解,sunyutian1998学长给我推荐的mlystdcall大佬的【教程】简易CDQ分治教程&学习笔记

  还有个B站小姐姐讲解的概念https://www.bilibili.com/video/av24295614?from=search&seid=4525696205028875575

  以下就是我在看完mlystdcall大佬的博客后,自己的一些思考。

  在学cdq分治的开始,我首先是把归并排序又看了一遍,归并排序也是一种分治的思想,把整个区间分成若干个区间处理,然后在合并。而cdq分治不同的地方就在于合并时,需要考虑左区间对右区间的影响。

  然后cdq分治一个比较简单的例子,归并排序求逆序对。求逆序对的话,数据量小我们直接可以暴力,而当数据量大时我们可以用线段树或者树状数组来维护,而当数据规模(也就比如给的数组中的数很大,每个都1e9)很大时,我们可以对数据进行离散化,然后再用线段树或者树状数组维护。当然我们也可以不用离散化,改用归并排序来处理这个问题,具体怎么做呢?

  首先,归并排序的话是先把每个区间分成[L,mid],[mid+1,R]两个区间,然后递归处理,这是分的过程。而当左右区间都处理好之后,这时我们就要把它们合并起来,归并排序就是简单的合并起来,而cdq分治就加上一个治的过程。因为我们是对逆序对,那么我们在合并的过程,对于L<=i<=mid,mid+1<=j<=R,有a[i]>a[j]的话,那么左区间在[i,mid]的数就都大于a[j],这时产生的逆序对就是mid-i+1个。也就是cdq分治思想中的

合并两个子问题,同时考虑到[L,M]内的修改对[M+1,R]内的查询产生的影响。即,用左边的子问题帮助解决右边的子问题。

Ultra-QuickSortOpenJ_Bailian - 2299

 #include<cstdio>
const int N=;
int a[N],temp[N];
long long ans=;
void merge(int l,int r)
{
if(l==r)
return ;
int m=(l+r)>>;
merge(l,m);
merge(m+,r);
//递归处理好左右区间后,左右区间都已经有序
int i=l,j=m+,k=l;
while(i<=m&&j<=r)
{
if(a[i]<=a[j])
temp[k++]=a[i++];
else
{
ans+=m-i+;
//如果a[i]>a[j]那么包括后面的m-i+1个数都会与a[j]产生逆序对
temp[k++]=a[j++];
}
}
while(i<=m)
temp[k++]=a[i++];
while(j<=r)
temp[k++]=a[j++];
for(i=l;i<=r;i++)
a[i]=temp[i];
}
int main()
{
int n;
while(~scanf("%d",&n)&&n)
{
for(int i=;i<n;i++)
scanf("%d",&a[i]);
ans=;
merge(,n-);
printf("%lld\n",ans);
}
return ;
}

归并排序求逆序对

  如果能明白归并排序求逆序对,那么对cdq分治的再深入,也没有太大问题。

  CDQ最基础的应用二维偏序问题

  给定N个有序对(a,b),求对于每个(a,b),满足a2<ab2<b的有序对(a2,b2)有多少个。

  在上面求逆序对中,其实我们就有涉及到二维偏序了,对于每个位置和相应的数视为有序对(位置i,数a[i])的话,那么逆序对,我们求的就是,满足位置i<位置j且a[i]>a[j]的有序对有多少个。由此扩展到二维偏序就是,我们可以先让a有序消除a元素的影响,然后合并问题时就是考虑b,也就相当于求顺序对。照着这个思想,我们可以去处理带修改和查询的问题。

  二维偏序问题的拓展

  给定一个N个元素的序列a,初始值全部为0,对这个序列进行以下两种操作:

  操作1:格式为1 x k,把位置x的元素加上k(位置从1标号到N)。

  操作2:格式为2 x y,求出区间[x,y]内所有元素的和。

  线段树或者树桩数组裸题,但用CDQ分治怎么做呢,我们把每个操作的发生时间,和操作位置视为有序队(操作时间,操作位置)的话,首先操作时间是有序的,然后就是对操作位置由小到大合并,这样当左边区间的位置小于右边时,我们就可以记录一个更新的前缀和,当左边区间的位置大于右边时,我们可以统计一个更新的值对查询的影响。

  那么说的话也就是,相应的值和操作类型的话我们需要用附加信息表示出来,op为1就代表着更新(加上k的操作),op为2就代表着查询操作中的减去[1,x-1]中的值,op为3就代表着查询操作中加上[1,y]中的值,op1和2合起来就是一整个查询操作。定义了这些之后我们就来考虑一下合并区间时,左右区间的影响。

  首先因为第一维操作发生时间已经有序了,那么左边区间的发生时间就比右边区间早,这样的话右边区间的更新对左边区间的查询就没有影响(因为左边区间的查询发生时,右边区间的更新还没发生呢),由此也有左边区间的更新对右边的查询就有影响,而相同的区间内的更新对查询的影响呢,因为它们已经合成一个区间了,那么它们的影响在合并时已经计算过了,所以我们最终要统计的就只是左边区间的更新对右边查询的影响。

  在具体的操作上就是,[L,M],[M+1,R],L<=i<=M,M+1<=j<=R这两个区间合并时,当a[i].pos<a[j].pos时,如果是a[i].op为1就记录左边区间更新的前缀和,而当a[i].pos>a[j].pos时,a[j].op为2就减去更新的前缀和,为3就加上更新的前缀和。而当a[i].pos=a[j].pos时,更新的优先级比查询高,因为我们前面所说的合并时就只需统计左边区间的更新对右边的查询就有影响,如果左区间的更新和右区间查询在同一个位置,应该先把左区间的更新算上。

  最后一个注意的地方就是,如果有初始值的话直接使初始值视为一个更新操作。

 #include<cstdio>
typedef long long ll;
const int N=,M=,Q=(M<<)+N;
struct Nop{
int pos,op,val;//更新的val代表加上的k,查询的val代表它的查询编号
friend bool operator <(const Nop &n1,const Nop &n2){
return n1.pos==n2.pos ? n1.op<n2.op : n1.pos<n2.pos;
}//位置由小到大合并,相同位置更新优先
}P[Q],temp[Q];//因为初始值视为更新操作,而查询操作分为两个
ll ans[M<<];
int pn,qn;//pn总操作编号,qn总查询编号
void cdq(int l,int r)
{
if(l==r)
return ;
int m=(l+r)>>;
cdq(l,m);
cdq(m+,r);
ll sum=;
int i=l,j=m+,k=l;
while(i<=m&&j<=r)
{
if(P[i]<P[j])
{
if(P[i].op==)//左边统计更新前缀和
sum+=1ll*P[i].val;
temp[k++]=P[i++];
}
else
{
if(P[j].op==)//右边处理查询
ans[P[j].val]-=sum;
else if(P[j].op==)
ans[P[j].val]+=sum;
temp[k++]=P[j++];
}
}
while(i<=m)//右边区间没有查询了,没必要继续统计前缀和
temp[k++]=P[i++];
while(j<=r)
{
if(P[j].op==)
ans[P[j].val]-=sum;
else if(P[j].op==)
ans[P[j].val]+=sum;
temp[k++]=P[j++];
}
for(i=l;i<=r;i++)
P[i]=temp[i];
}
int main()
{
int n,m,op,x,y;
while(~scanf("%d%d",&n,&m))
{
pn=qn=;
for(int i=;i<=n;i++)
{
P[pn].pos=i,P[pn].op=;
scanf("%d",&P[pn++].val);
}
for(int i=;i<=m;i++)
{
scanf("%d%d%d",&op,&x,&y);
if(op==)
P[pn].op=,P[pn].pos=x,P[pn++].val=y;
else//把查询拆分成两个操作
{
P[pn].op=,P[pn].pos=x-,P[pn++].val=qn;
P[pn].op=,P[pn].pos=y,P[pn++].val=qn++;
}
}
cdq(,pn-);
for(int i=;i<qn;i++)
printf("%lld\n",ans[i]);
}
return ;
}

分分治治

  如果你能耐住性子坚持到现在,那么不用我说,也可以发现,CDQ分治的前提是,更新对查询的贡献独立,更新彼此间互不影响,而且的允许离线算法,所有的操作都得先记录下来再处理。看起来,CDQ的时间和空间复杂度都不怎么低,而且CDQ分治做的线段树和树状数组好像都能做,那为什么还需要CDQ分治呢?这也是我们前面说的,当数据规模很大时,无论是空间和时间,线段树和树状数组都吃不消,而CDQ分治取决于数据量不取决于数据规模。此外,当更复杂一点,三维偏序,四维偏序等问题的时候,线段树或者树状数组就得树套树之类的,处理起来非常麻烦,而CDQ分治就可以顶替复杂的高级数据结构,能对问题起到一个降维的效果。

  至于更深的三维,四维问题,当做到相应的题时再更相应的内容。希望我的博客能对你学习CDQ分治有所帮助。

  最后,路不只一条,解决问题的方法也不只一种,当一种不行时就去尝试其他的。

CDQ分治学习思考的更多相关文章

  1. 初学cdq分治学习笔记(可能有第二次的学习笔记)

    前言骚话 本人蒟蒻,一开始看到模板题就非常的懵逼,链接,学到后面就越来越清楚了. 吐槽,cdq,超短裙分治....(尴尬) 正片开始 思想 和普通的分治,还是分而治之,但是有一点不一样的是一般的分治在 ...

  2. CDQ分治学习笔记

    数据结构中的一块内容:$CDQ$分治算法. $CDQ$显然是一个人的名字,陈丹琪(NOI2008金牌女选手) 这种离线分治算法被算法界称为"cdq分治" 我们知道,一个动态的问题一 ...

  3. cdq分治学习

    看了stdcall大佬的博客 传送门: http://www.cnblogs.com/mlystdcall/p/6219421.html 感觉cdq分治似乎很多时候都要用到归并的思想

  4. [摸鱼]cdq分治 && 学习笔记

    待我玩会游戏整理下思绪(分明是想摸鱼 cdq分治是一种用于降维和处理对不同子区间有贡献的离线分治算法 对于常见的操作查询题目而言,时间总是有序的,而cdq分治则是耗费\(O(logq)\)的代价使动态 ...

  5. CDQ分治学习笔记(三维偏序题解)

    首先肯定是要膜拜CDQ大佬的. 题目背景 这是一道模板题 可以使用bitset,CDQ分治,K-DTree等方式解决. 题目描述 有 nn 个元素,第 ii 个元素有 a_iai​.b_ibi​.c_ ...

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

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

  7. 学习笔记 | CDQ分治

    目录 前言 啥是CDQ啊(它的基本思想) 例题 后记 参考博文 前言 博主太菜了 学习快一年的OI了 好像没有什么会的算法 更寒碜的是 学一样还不精一样TAT 如有什么错误请各位路过的大佬指出啊感谢! ...

  8. cdq分治入门and持续学习orz

    感觉cdq分治是一个很有趣的算法 能将很多需要套数据结构的题通过离线来做 目前的一些微小的理解 在一般情况下 就像求三维偏序xyz 就可以先对x排序 然后分治 1 cdq_x(L,M) ; 2 提取出 ...

  9. 【教程】简易CDQ分治教程&学习笔记

    前言 辣鸡蒟蒻__stdcall终于会CDQ分治啦!       CDQ分治是我们处理各类问题的重要武器.它的优势在于可以顶替复杂的高级数据结构,而且常数比较小:缺点在于必须离线操作. CDQ分治的基 ...

随机推荐

  1. 【xlwings】 wps 和 office 的excel creat_sheet区别

    最近在学习 xlwings,参考学习的网址:https://www.jianshu.com/p/b534e0d465f7 写得很棒,学到了很多. 在新建sheet表单, 发现一个问题. import ...

  2. linux命令 ip

  3. Linux7_MySQL5.7_主从复制_scripts

    # cat my_full_backup.sh #!/bin/bash BEGINTIME=`date +"%Y-%m-%d %H:%M:%S"` format_time=`dat ...

  4. centos中mariadb的相关操作

    Tip 1 在使用mariadb中启动服务报错 : Failed to start mariadb.service: Unit not found. 解决办法: yum install -y mari ...

  5. 【原创】大叔问题定位分享(35)spring中session失效时间

    spring项目中将sessionid对应的cookie过期时间设置很长,但是实际session还是在半个小时后失效,跟了一下代码,spring中session实现接口为 org.springfram ...

  6. kali安装dnsdict6

    https://src.fedoraproject.org/lookaside/pkgs/thc-ipv6/thc-ipv6-2.7.tar.gz/2975dd54be35b68c140eb2a6b8 ...

  7. LeetCode 腾讯精选50题--数组中的第K个最大元素

    好吧,不得不承认,书上看到的始终不是自己的,只有亲身时间过才会明白该怎么操作. 找数组中第K个最大元素,简而言之就是先排序,不论使用哪种算法,都需要先排序,确认位置,由于数组可以通过下标直接访问,所以 ...

  8. vue 超大 table

    https://github.com/ColdDay/vue-fast-table https://coldday.github.io/vue-fast-table/dist/index.html h ...

  9. (转)Android刷机的一些知识整理

    刷机概述刷机原因刷机可以升级和破解固件(在Android上:即可以升级系统,更改系统,获取Root权限):破解系统的原因①安装第三方软件不需要签名,不受证书的束缚:②修改系统的文件,达到系统的瘦身,以 ...

  10. Win7自带的系统备份还原功能如何去使用?

    很多用户都会反映Win7系统使用过程中会出现系统或应用程序方面的小故障,针对这些小问题,再选择进行电脑系统的重装就有些过于麻烦了. 其实Win7系统内带有系统备份和还原的功能,可以在电脑系统出现小问题 ...