先挂上个大佬讲解,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. Redis 键空间事件通知

    出处: 使用Redis完成定时任务 场景   使用Java做过项目的人大概都用过定时器.一般来说,项目里订单模块和评论模块,都会涉及到定时任务执行.比如说: 用户下订单后,需要在5分钟内完成支付,否则 ...

  2. mybatis数组和集合的长度判断及插入

    1.在使用foreach的是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下4种情况: 如果传入的是单参数且参数类型是一个List的时候,collect ...

  3. jq之display:none与visible:hidden

    http://www.cnblogs.com/linxiong945/p/4075146.html 今天学习到jquery的hide()部分时,突然有一个想法,jquery中的隐藏/显示部分的实现是给 ...

  4. bash 中 () {} [] [[]] (()) 的解释

    bash 中 () {} [] [[]] (()) 的解释 来源  https://www.cnblogs.com/fhefh/archive/2011/04/16/2017895.html bash ...

  5. span 如何移除点击事件

    //设置点击事件不可用 $("#verificode").css("pointer-events", "none"); //倒计时完毕,点击 ...

  6. 110G离线维基百科数据免费拿

    110G离线维基百科数据免费拿.. 资料获取方式,关注公总号RaoRao1994,查看往期精彩-所有文章,即可获取资源下载链接 更多资源获取,请关注公总号RaoRao1994

  7. 币种大写算法(js)

    注意事项:小数精度处理问题,n*10出现精度误差,如1.88*10计算得18.799999999999997,实际想要的数据是18.8: 思路一:小数变成整数(通过字符串处理),计算后,变成小数: 思 ...

  8. 获得npm server 上 package 的版本信息

    通过这个命令可以获取package 的历史版本信息 npm view packagename   versions

  9. Delphi BuildCommDCBAndTimeouts函数

  10. Delphi RS-232C标准