cdq分治是一种分治算法:

一种分治思想,必须离线,可以用来处理序列上的问题(比如偏序问
题),还可以优化1D/1D类型的DP。
• 算法的大体思路我们可以用点对来描述。假定我们有一个长度为n的序列,要处理序列中元素点对间的关系。定义一个操作cdq(l,r)表示当前处理序列上区间[L,R]的点对关系。那么我们需要找到[L,R]的中点M,将不同的点对分为三类:
• A:两个点都在区间[L,M]上
• B:两个点都在区间[M+1,R]上
• C:两个点分别在[L,M]和[M+1,R]上。
对于前两种情况,分别用cdq(L,M),cdq(M+1,R)解决,也就是甩锅给下一层。
对于第三种情况,我们需要想方设法在当前层的cdq(L,R)中解决。
在解决当前层的问题时,利用已经处理好的左右两边的信息,具体视不同题目而
定。

其实,这样的算法很帅!

例题:

A. 陌上花开

 

题目描述

有n朵花,每朵花有三个属性:花形(s)、颜色(c)、气味(m),用三个整数表示。
现在要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量。
定义一朵花A比另一朵花B要美丽,当且仅Sa>=Sb,Ca>=Cb,Ma>=Mb。显然,两朵花可能有同样的属性。需要统计出评出每个等级的花的数量。

这道题就是一道裸的cdq模板题,但是对于初学的菜鸡(就是我)来说还是异常的艰难,我们知道对于普通的二维偏序问题,我们的求解思路就是归并排序(有时也不一定),所以当求解三维偏序的时候我们还是要借鉴二维偏序的思路,求解的时候先进行三元组排序,然后在递归过程中将后两元排序,然后使用一个数据结构来维护第三维偏序,进行求解,其实可以把这道题当作求解三维偏序的板子,其实遇到三维偏序的题就可以想一想是不是cdq分治;

本道题的板子实现是依靠一堆排序实现的,我因为懒得打(没错,本人很懒)归并排序,所以就是用STL中的sort,从而时间上比别人多了一个log,但还是A了,就是慢的一匹

• 首先将所有元素三元组排序并去重,那么权值a已经随下标有序。假设我们当前处理区间[L,R]内的元素。设M为区间中点,我们分别在[L,M][M+1,R]两个区间中对第二维排序,那么[L,M]内所有元素的第一维一定小于[M+1,R]的元素,维护一个单调指针,按照第二维权值从小到大将[L,M]的元素加入树状数组,同时在树状数组中查询前缀和即可保障三个维度都是小于关系。具体实现中,我们可以在递归处理的同时完对第二维的归并排,而不用额外消耗时间使用快速排序

如果还是很蒙蔽,那就看代码!

 inline void cdq(int l,int r)
{
if(l==r) return;
int m=l+r>>,pl=l,pr=m+,p=l;
cdq(l,m),cdq(m+,r);//递归处理两个子区间
while(pl<=m&&pr<=r)
{
if(s[pl].b<=s[pr].b) q[p++]=s[pl++];
else q[p]=s[pr++],mk[p++]=;
}
while(pl<=m) q[p++]=s[pl++];
while(pr<=r) q[p]=s[pr++],mk[p++]=;
for(int i=l;i<=r;++i)
{
if(mk[i]) res[q[i].id]+=bit.sum(q[i].c);
else bit.add(q[i].c,q[i].w);
}
for(int i=l;i<=r;++i)
{
if(!mk[i]) bit.add(q[i].c,-q[i].w);
else mk[i]=;
s[i]=q[i];
}
}

就是这个,这就是cdq的核心部分,我在使用cdq的时候也认识到了分治算法的重要性,之前一直都吧递归的程序以及函数拒之门外,但是其实他们的效率也不差,而且好想是真的;(但是研发一个新的算法还是不容易的!)%%%cdq

然后就是我的多个log,跑的极慢的程序:

 #include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
struct re{int a,b,c,cnt,ans;}s[],v[];
int cnt[],q[],tree[],ans[];
int tott,n,k;
inline int read()
{
int ss=;char bb=getchar();
while(bb<||bb>)bb=getchar();
while(bb>=&&bb<=)ss=(ss<<)+(ss<<)+(bb^),bb=getchar();
return ss;
}
int lowbit(int x){return x&(-x);}
bool cmp(re a,re b){return a.b<b.b||(a.b==b.b&&a.c<b.c);}
bool cmp2(re a,re b){return a.a<b.a||(a.a==b.a&&cmp(a,b));}
void upd(int x,int vv){while(x<=k){tree[x]+=vv;x+=lowbit(x);}}
int query(int x){int sum=;while(x){sum+=tree[x];x-=lowbit(x);}return sum;}
void cdq(int l,int r)
{
if(l==r)return ;
int mid=(l+r)>>;
cdq(l,mid);
cdq(mid+,r);
sort(v+l,v+mid+,cmp);
sort(v+mid+,v+r+,cmp);
int l1=l,l2=mid+;
while(l2<=r)
{
while(l1<=mid&&v[l1].b<=v[l2].b)upd(v[l1].c,v[l1].cnt),++l1;
v[l2].ans+=query(v[l2].c);++l2;
}
for(int i=l;i<l1;i++)upd(v[i].c,-v[i].cnt);
}
int main()
{
//freopen("ccf.txt","r",stdin);
n=read(),k=read();
for(int i=;i<=n;i++)
s[i].a=read(),s[i].b=read(),s[i].c=read();
sort(s+,s++n,cmp2);
for(int i=,j=;i<=n;i=j)
{
v[++tott]=s[i];
while(s[i].a==s[j].a&&s[i].b==s[j].b&&s[i].c==s[j].c&&j<=n)
j++,v[tott].cnt++;
}
cdq(,tott);
for(int i=;i<=tott;i++)cnt[v[i].ans+v[i].cnt-]+=v[i].cnt;
for(int i=;i<n;i++)printf("%d\n",cnt[i]);
return ;
}

cdq1

其实还是建议使用在递归的同时进行的排序,毕竟有的时候有些题并不那么友好。sort在平时水题的时候还是很好使的。但是效率实在是不高:

对比鲜明!

cdq在其他的地方(也就是题不那么板子的时候)也有出现!

例题2:

B. Mokia

题目类型:传统 评测方式:文本比较
 

题目描述

维护一个W*W的矩阵,初始值均为S.每次操作可以增加某格子的权值,或询问某子矩阵的总权值.修改操作数M<=160000,询问数Q<=10000,W<=2000000.

输入格式

第一行两个整数,S,W;其中S为矩阵初始值;W为矩阵大小
接下来每行为一下三种输入之一(不包含引号):
"1 x y a"
"2 x1 y1 x2 y2"
"3"
输入1:你需要把(x,y)(第x行第y列)的格子权值增加a
输入2:你需要求出以左下角为(x1,y1),右上角为(x2,y2)的矩阵内所有格子的权值和,并输出
输入3:表示输入结束

这道题首先一看,就先是想到之前IOI的那倒移动电话那道题。然后二维线段树开吗。等等,空间根本开不下啊,而且时间也会爆炸那怎么啊?玄学树套树....?不存在的,我也不会码啊!这是cdq就出场了,cdq是维护三维偏序问题,所以这道题当然可以抽象成三维偏序问题,只是稍变一下,按照题目的意思,这个矩形是可以有四个矩形进行容斥得到,所以我们一就是求对于x,y,满足三维偏序的点对的个数问题,cdq就直接搞定了!至此,这道题已经和前一题的三维偏序基本相同,不同点在于修改操作。那么我们改变策略,在分出两个子区间后,只考虑左区间的修改,对于左右端点都在右区间的查询操作产生的影响。按照这种分法,一定能保证所有查询操作能考虑到先前的修改操作。

所以这还是一道cdq的半裸题;

 #include<cstdio>
#include<queue>
#include<cctype>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=;
struct num{
int x,y,add,t,pos;
}G[maxn],ss[maxn];
int s,w,cnt=,t=,ans[maxn],tree[maxn];
#define debug(x) cout<<x<<" debug!"<<endl;
bool cmp(num a,num b)
{
if(a.x==b.x&&a.y==b.y)return a.pos<b.pos;
if(a.x==b.x)return a.y<b.y;
return a.x<b.x;
}
inline int lowbit(int t){return t&(-t);}
void add(int x,int y){while(x<=w){tree[x]+=y;x+=lowbit(x);}}
int query(int x){int res=;while(x){res+=tree[x];x-=lowbit(x);}return res;}
void addq(int x1,int y1,int x2,int y2)
{
int pos=++cnt;
G[++t].pos=pos;G[t].t=t;G[t].x=x2;G[t].y=y2;G[t].add=;
G[++t].pos=pos;G[t].t=t;G[t].x=x1-;G[t].y=y1-;G[t].add=;
G[++t].pos=pos;G[t].t=t;G[t].x=x2;G[t].y=y1-;G[t].add=-;
G[++t].pos=pos;G[t].t=t;G[t].x=x1-;G[t].y=y2;G[t].add=-;
}
void cdq(int l,int r)
{
if(l>=r)return;
int mid=(l+r)>>;
for(int i=l;i<=r;i++)
{
if(G[i].t<=mid&&!G[i].pos)add(G[i].y,G[i].add);
if(G[i].t>mid&&G[i].pos)ans[G[i].pos]+=G[i].add*query(G[i].y);
}
for(int i=l;i<=r;i++)
{
if(G[i].t<=mid&&!G[i].pos)add(G[i].y,-G[i].add);
}
int l1=l,l2=mid+;
for(int i=l;i<=r;i++)
{
if(G[i].t<=mid)ss[l1++]=G[i];
else ss[l2++]=G[i];
}
for(int i=l;i<=r;i++)G[i]=ss[i];
cdq(l,mid);
cdq(mid+,r);
return;
}
int main()
{
scanf("%d%d",&s,&w);
while()
{
int k;
scanf("%d",&k);
if(k==)break;
if(k==)
{
int xx,yy,zz;
scanf("%d%d%d",&xx,&yy,&zz);
//debug(xx);debug(yy),debug(zz);
G[++t].x=xx;
G[t].y=yy;
G[t].add=zz;
G[t].t=t;
}
else
{
int x1,x2,y1,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
addq(x1,y1,x2,y2);
}
}
sort(G+,G+t+,cmp);
cdq(,t);
for(int i=;i<=cnt;i++)printf("%d\n",ans[i]);
return ;
}

这道题也告诉我们其实很多题都是换汤不换药,只是吧题目背景换一下,别的套路还是一样的,但是不一定所有的都一样;

例题3:

C. 拦截导弹

入输出
题目类型:传统 评测方式:Special Judge
 

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度、并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高于前一发的高度,其拦截的导弹的飞行速度也不能大于前一发。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

在不能拦截所有的导弹的情况下,我们当然要选择使国家损失最小、也就是拦截导弹的数量最多的方案。但是拦截导弹数量的最多的方案有可能有多个,如果有多个最优方案,那么我们会随机选取一个作为最终的拦截导弹行动蓝图。

我方间谍已经获取了所有敌军导弹的高度和速度,你的任务是计算出在执行上述决策时,每枚导弹被拦截掉的概率。

输入格式

第一行包含一个正整数

,表示敌军导弹数量;

下面 行按顺序给出了敌军所有导弹信息:

第i+1行包含2个正整数

,分别表示第 枚导弹的高度和速度。

输出格式

输出包含两行。

第一行为一个正整数,表示最多能拦截掉的导弹数量;

第二行包含n个0到1之间的实数,第i个数字表示第i枚导弹被拦截掉的概率(你可以保留任意多位有效数字)。

相信这会使我们想到之前做的线性dp,但是那是这道题的简化办,就只有第一问,但是这有第二问,我们也发现之前只有一个两个限制条件,就是时间和高度,但是这道题有三个限制条件,分别是时间,高度和速度,这三个只有都满足的时候才能够对答案作出贡献,所以cdq分治,因为是三维偏序。然后就是要把dp的状态转移放到cdq的里面去,因为是让球概率,那么这也是一道假的概率题,只要使用合法的方案数比上总的方案数就可以得到答案;然后就要正着和倒着都各跑一遍,(这也是为了球总的方案数,也很好像),然后在递归过程中进行状态转移就行了!

 #include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
const int maxn=;
inline int read()
{
int x=,f=;char cc;cc=getchar();
while(cc>''||cc<''){if(cc=='-')f=-;cc=getchar();}
while(cc>=''&&cc<=''){x=(x<<)+(x<<)+(cc^);cc=getchar();}
return x;
}
struct tree
{
int f;double w;
tree(){f=,w=;}
}t[maxn];
int n;
int st[maxn],top=,th,tv;
inline int lowbit(int x){return x&(-x);}
inline void add(int p,int f,double w)
{
while(p<n)
{
if(t[p].f<f)
{
if(t[p].f==)st[++top]=p;
t[p].f=f;t[p].w=w;
}
else if(t[p].f==f)t[p].w+=w;
p+=lowbit(p);
}
return;
}
tree ask(int p)
{
tree res;
while(p)
{
if(t[p].f>res.f) res=t[p];
else if(t[p].f==res.f) res.w+=t[p].w;
p-=lowbit(p);
}
return res;
}
struct Dan
{
int h,v,f[],id,t;
double g[];
}a[maxn],q[maxn];
int wh[maxn],wv[maxn],id[maxn];
int rk[maxn];
inline bool cmp(int x,int y){return a[x].h<a[y].h||(a[x].h==a[y].h&&a[x].id<a[y].id);}
int cmpid(Dan a,Dan b){return a.id<b.id;}
int cnt=;
void cdq(int l,int r,int mode)
{
if(l==r)
{
if(a[l].f[mode]<){a[l].f[mode]=;a[l].g[mode]=;}
return;
}
int mid=(l+r)>>;
memcpy(q+l,a+l,sizeof(Dan)*(r-l+));
int q1=l,q2=mid+;
for(int i=l;i<=r;i++)
if(q[i].t<=mid)a[q1++]=q[i];
else a[q2++]=q[i];
cdq(l,mid,mode);
q1=l;
for(int i=mid+;i<=r;i++)
{
while(q1<=mid && a[q1].id<a[i].id)
add(a[q1].v,a[q1].f[mode],a[q1].g[mode]),q1++;
tree res=ask(a[i].v);
if(!res.f)continue;
if(res.f+>a[i].f[mode])
{
a[i].f[mode]=res.f+;
a[i].g[mode]=res.w;
}
else if(res.f+==a[i].f[mode]) a[i].g[mode]+=res.w;
}
while(top){t[st[top]].w=;t[st[top--]].f=;}
cdq(mid+,r,mode);
merge(a+l,a+mid+,a+mid+,a+r+,q+l,cmpid);
memcpy(a+l,q+l,sizeof(Dan)*(r-l+));
return;
}
int main()
{
//freopen("cnm.txt","r",stdin);
n=read();
for(int i=;i<=n;i++)
{
a[i].h=read();a[i].v=read();a[i].id=i;
wh[i]=a[i].h;wv[i]=a[i].v;
rk[i]=i;
}
sort(wh+,wh+n+);
sort(wv+,wv+n+);
th=unique(wh+,wh+n+)-wh-;
tv=unique(wv+,wv+n+)-wv-;
for(int i=;i<=n;i++)
{
a[i].h=th-(lower_bound(wh+,wh+th+,a[i].h)-wh)+;
a[i].v=tv-(lower_bound(wv+,wv+tv+,a[i].v)-wv)+;
}
sort(rk+,rk+n+,cmp);
for(int i=;i<=n;i++)a[rk[i]].t=i;
cdq(,n,);
for(int i=;i<=n;i++)
{
a[i].h=th-a[i].h+;
a[i].v=tv-a[i].v+;
a[i].id=n-a[i].id+;
a[i].t=n-a[i].t+;
}
reverse(a+,a+n+);
cdq(,n,);
reverse(a+,a+n+);
double smm=;
int ans=;
for(register int i=;i<=n;i++)
ans=max(ans,a[i].f[]+a[i].f[]-);
printf("%d\n",ans);
for(int i=;i<=n;i++)
if(a[i].f[]==ans)
smm+=a[i].g[]*a[i].g[]*1ll;
for(int i=;i<=n;i++)
{
double res=a[i].g[]*a[i].g[];
if(a[i].f[]+a[i].f[]-!=ans)printf("%.5lf ",0.0);
else printf("%.5f ",res/smm);
}
return ;
}

lan

总结cdq的用处很大最起码不用使用复杂的树套树来维护一些东西,但是目前我知道的cdq好像都只能球三维偏序问题。

/////////////cdq专题我还没刷完,这个专题的坑还很大///////

「分治」-cdq分治的更多相关文章

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

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

  2. 2090. 「ZJOI2016」旅行者 分治,最短路

    2090. 「ZJOI2016」旅行者 链接 loj 思路 \((l,mid)(mid+1,r)\).考虑跨过mid的贡献. 假设选的中间那条线的点为gzy,贡献为\(dis(x,gzy)+dis(g ...

  3. hdu 5126 stars cdq分治套cdq分治+树状数组

    题目链接 给n个操作, 第一种是在x, y, z这个点+1. 第二种询问(x1, y1, z1). (x2, y2, z2)之间的总值. 用一次cdq分治可以将三维变两维, 两次的话就变成一维了, 然 ...

  4. 「SNOI2019」通信 分治建图

    根据题意 每个点可以直接与S,T相连 也可以和前面的哨站相连 暴力建边的话 有n2条边 要用分治优化建边: 类似于归并排序 先对每一层分为左半边与右半边 对每一半都拿出来先排序去重后 直接排成一条链建 ...

  5. 「SNOI2019」通信 分治优化费用流建图

    题意: n 个排成一列的哨站要进行通信.第 i 个哨站的频段为 ai. 每个哨站 ii 需要选择以下二者之一: 1.直接连接到控制中心,代价为 W:2.连接到前面的某个哨站 j(j<i),代价为 ...

  6. BZOJ3745 / SP22343 NORMA2 - Norma 分治,CDQ分治

    要命的题目. 写法:分类讨论进行计算. 枚举过每一个\(mid\)的所有区间.对于左端点\(i∈[l, mid - 1]\),向左推并计算\([l,mid]\)范围内的最大\(/\)最小值. 然后右端 ...

  7. bzoj 3672 利用点分治将CDQ分治推广到树型结构上

    最大的收获就是题目所说. deal(s) : 处理节点s所在块的问题,并保证: 1.s是该块中最靠近根节点的点,没有之一. 2.s所在块到根节点的路径上的点全都用来更新过了s所在块的所有节点. 然后步 ...

  8. bzoj3295: [Cqoi2011]动态逆序对(cdq分治)

    #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #i ...

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

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

随机推荐

  1. slf4j输出变量

    花括号表示占位符,推荐使用

  2. 多线程下的wait为什么可以不需要notify

    多线程下的wait方法就像我无处安放的青春,胡乱来,感觉没有一点套路.wait后不需要notify仍可以继续执行.所以我决定看看到底咋回事..... 先结合join方法了解一下. join方法是可以等 ...

  3. Salesforce学习之路-developer篇(四)Visualforce结合Reports展示图表

    Salesforce作为一款CRM系统,个人觉得最重要的环境便是在于数据的展示和联动,而Salesforce也本身提供了相当强大的功能,Report在展示图表的方面十分强大,前段时间更是宣布以157亿 ...

  4. JMXtrans + InfluxDB + Grafana实现Zookeeper性能指标监控

    一.总体效果图 这里是将集群全部放在一起,可以根据自己的审美看怎么放 二.监控指标 其中有些指标与第一篇Zookeeper通过四字命令基础监控(Zabbix)的四字命令的指标是有重复的,二者选一个则可 ...

  5. thinkphp5框架之请求

    又看到请求这一部分,个人认为这部分是算重要的一部分 单独记一篇笔记. 0x01 request请求对象 如果要获取当前的请求信息,可以使用\think\Request类,完全开发手册中也有提到,继承系 ...

  6. POJ2828 Buy Tickets 树状数组

    Description Railway tickets were difficult to buy around the Lunar New Year in China, so we must get ...

  7. opencv::模板匹配(Template Match)

    模板匹配介绍 模板匹配就是在整个图像区域发现与给定子图像匹配的小块区域. 所以模板匹配首先需要一个模板图像T(给定的子图像) 另外需要一个待检测的图像-源图像S 工作方法,在带检测图像上,从左到右,从 ...

  8. std::shared_future/future

    std::future提供了一种访问异步操作结果的机制.

  9. Java Web 学习(1) —— Servlet

    Java Web 学习(1) —— Servlet 一. 什么是 Servlet Java Servlet 技术是Java体系中用于开发 Web 应用的底层技术. Servlet 是运行在 Servl ...

  10. 二叉树的查找(前序、中序、后序、层序遍历)--biaobiao88

    建立一棵含有n个结点的二叉树,采用二叉链表存储: 输出前序.中序.后序..层序遍历该二叉树的遍历结果. 定义二叉树的数据类型——二叉树结点结构体BiNode.建立二叉链表可以采用扩展二叉树的一个遍历序 ...