「分治」-cdq分治
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. 拦截导弹
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度、并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高于前一发的高度,其拦截的导弹的飞行速度也不能大于前一发。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
在不能拦截所有的导弹的情况下,我们当然要选择使国家损失最小、也就是拦截导弹的数量最多的方案。但是拦截导弹数量的最多的方案有可能有多个,如果有多个最优方案,那么我们会随机选取一个作为最终的拦截导弹行动蓝图。
我方间谍已经获取了所有敌军导弹的高度和速度,你的任务是计算出在执行上述决策时,每枚导弹被拦截掉的概率。
输入格式
第一行包含一个正整数
,表示敌军导弹数量;
下面 行按顺序给出了敌军所有导弹信息:
第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分治的更多相关文章
- COGS 2479. [HZOI 2016]偏序 [CDQ分治套CDQ分治 四维偏序]
传送门 给定一个有n个元素的序列,元素编号为1~n,每个元素有三个属性a,b,c,求序列中满足i<j且ai<aj且bi<bj且ci<cj的数对(i,j)的个数. 对于100%的 ...
- 2090. 「ZJOI2016」旅行者 分治,最短路
2090. 「ZJOI2016」旅行者 链接 loj 思路 \((l,mid)(mid+1,r)\).考虑跨过mid的贡献. 假设选的中间那条线的点为gzy,贡献为\(dis(x,gzy)+dis(g ...
- hdu 5126 stars cdq分治套cdq分治+树状数组
题目链接 给n个操作, 第一种是在x, y, z这个点+1. 第二种询问(x1, y1, z1). (x2, y2, z2)之间的总值. 用一次cdq分治可以将三维变两维, 两次的话就变成一维了, 然 ...
- 「SNOI2019」通信 分治建图
根据题意 每个点可以直接与S,T相连 也可以和前面的哨站相连 暴力建边的话 有n2条边 要用分治优化建边: 类似于归并排序 先对每一层分为左半边与右半边 对每一半都拿出来先排序去重后 直接排成一条链建 ...
- 「SNOI2019」通信 分治优化费用流建图
题意: n 个排成一列的哨站要进行通信.第 i 个哨站的频段为 ai. 每个哨站 ii 需要选择以下二者之一: 1.直接连接到控制中心,代价为 W:2.连接到前面的某个哨站 j(j<i),代价为 ...
- BZOJ3745 / SP22343 NORMA2 - Norma 分治,CDQ分治
要命的题目. 写法:分类讨论进行计算. 枚举过每一个\(mid\)的所有区间.对于左端点\(i∈[l, mid - 1]\),向左推并计算\([l,mid]\)范围内的最大\(/\)最小值. 然后右端 ...
- bzoj 3672 利用点分治将CDQ分治推广到树型结构上
最大的收获就是题目所说. deal(s) : 处理节点s所在块的问题,并保证: 1.s是该块中最靠近根节点的点,没有之一. 2.s所在块到根节点的路径上的点全都用来更新过了s所在块的所有节点. 然后步 ...
- bzoj3295: [Cqoi2011]动态逆序对(cdq分治)
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #i ...
- [偏序关系与CDQ分治]【学习笔记】
组合数学真是太棒了 $CDQ$真是太棒了(雾 参考资料: 1.<组合数学> 2.论文 课件 很容易查到 3.sro __stdcall 偏序关系 关系: 集合$X$上的关系是$X$与$X$ ...
随机推荐
- e课表项目第二次冲刺周期第六天
昨天干了什么? 昨天是这次冲刺周期的第五天,我们的冲刺周期已经快过了一半,我们已经实现了对第一层界面的设计,所以我们的进度和我们的时间正好吻合,所以我们有信心完成我们的软件.我在网上搜了一些关于监听的 ...
- 树莓派(4B)Linux + .Net Core嵌入式-初始化环境(一)
一.树莓派简单介绍 二.软&件准备 硬件:树莓派4B主机.电源线.TF卡&TF读卡器.PC 软件:win32diskimager(用于烧系统到TF卡).Xshell或Windows T ...
- Redis学习三(进阶功能).
一.排序 redis 支持对 list,set 和 zset 元素的排序,排序的时间复杂度是 O(N+M*log(M)).(N 是集合大小,M 为返回元素的数量) sort key [BY patte ...
- Java8两大特性(一)——Stream
什么是Stream? Stream(流)是一个来自数据源的元素队列并且支持聚合操作,元素流在管道中经过中间操作,最终操作得到结果. 数据源:集合,数组,I/O channel,产生器generator ...
- Qt5教程: (2) 信号与槽
1. 新建工程 新建一个"Qt Widgets Application"工程 2. 添加按钮 一个Qt工程会有很多个控件, 如果把逻辑代码都写在main函数里, main函数会非常 ...
- Web安全之URL跳转科普
跳转无非是传递过来的参数未过滤或者过滤不严,然后直接带入到跳转函数里去执行. 0x01 JS js方式的页面跳转1.window.location.href方式 <script language ...
- phpstorm 设置换行符的格式
菜单 > 文件 > 设置
- [NOIp2014] luogu P2312 解方程
题目描述 已知方程∑i=0naixi=0\sum_{i=0}^{n}{a_ix^i}=0i=0∑naixi=0求该方程在 [1,m][1,m][1,m] 内的整数解. Solution 有一个秦九 ...
- 手机号码格式化显示javascript
/*手机代码格式化一般与Object.oninput=function(){}连用*/ function phoneFormat(phone){ if(phone.length <= 3 ){ ...
- springboot使用idea打jar包
1.如果有本地jar包依赖,放到/WEB-INF/lib下,用system类型配到pom中 <dependency> <groupId>org.wechat</group ...