CDQ分治–用时间降维的美丽算法


CDQ分治,网上的阐述很多,太专业性的文字我就不赘述,这里指谈谈自己的感受

还是%一下CDQ大神的论文

CDQ分治的主要想法就是降维(比如三维问题降维到二维问题),并付出O(logn)" role="presentation">O(logn)O(logn)的代价

前提:支持离线


那么我们思考一下经典二维偏序问题:

给定数列a和b,问(i&lt;j)" role="presentation">(i<j)(i<j)满足ai&lt;=aj,bi&lt;=bj" role="presentation">ai<=aj,bi<=bjai<=aj,bi<=bj的组数

我们把每一个i对应的ai,bi" role="presentation">ai,biai,bi当做二维平面上的点

并以x坐标为比较函数进行排序,使得对于任意i&lt;j" role="presentation">i<ji<j,满足ai&lt;=aj" role="presentation">ai<=ajai<=aj,这样我们只需要讨论b的情况,但是暴力跑一遍显然是不够优秀的

采用分治思想

将区间[L,R]" role="presentation">[L,R][L,R]分成[L,mid]" role="presentation">[L,mid][L,mid]和[mid+1,R]" role="presentation">[mid+1,R][mid+1,R],先递归处理子问题,再考虑当前情况下左区间对右区间的贡献有多少

把左右区间按照b的值排序,这个时候左区间所有点的a严格小于等于右区间所有数的a,所以我们可以直接双指针计算一下贡献

每一层的时间效率都是O(n)" role="presentation">O(n)O(n),一共有log层,于是时间复杂度是O(nlogn)" role="presentation">O(nlogn)O(nlogn)


思考一下如果是三维的情况怎么办?

我们先对x排序,然后我们发现这样的问题转化成了二维问题,然后就可以套用二维偏序的方法了

BZOJ3262

#include<bits/stdc++.h>
using namespace std;
#define N 100010
#define K 200010
struct BIT{
int t[K];
void add(int x,int vl){
while(x<K){
t[x]+=vl;
x+=x&(-x);
}
}
int query(int x){
int ans=0;
while(x){
ans+=t[x];
x-=x&(-x);
}
return ans;
}
}T;
struct Node{int a,b,c,cnt,ans;}p[N];
int n,k,ans[N];
inline bool cmp1(Node a,Node b){
if(a.a==b.a&&a.b==b.b)return a.c<b.c;
if(a.a==b.a)return a.b<b.b;
return a.a<b.a;
}
inline bool cmp2(Node a,Node b){
if(a.b==b.b)return a.c<b.c;
return a.b<b.b;
}
void solve(int l,int r){
if(l==r){p[l].ans=p[l].cnt;return;}
int mid=(l+r)>>1;
solve(l,mid);
solve(mid+1,r);
sort(p+l,p+mid+1,cmp2);
sort(p+mid+1,p+r+1,cmp2);
int tl=l,tr=mid+1;
while(tr<=r){
while(tl<=mid&&p[tl].b<=p[tr].b)T.add(p[tl].c,p[tl].cnt),tl++;
p[tr].ans+=T.query(p[tr].c);
tr++;
}
for(int i=l;i<tl;i++)T.add(p[i].c,-p[i].cnt);
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&p[i].a,&p[i].b,&p[i].c);
sort(p+1,p+n+1,cmp1);
int newn=0;
for(int i=1;i<=n;i++){
if(p[i].a!=p[newn].a||p[i].b!=p[newn].b||p[i].c!=p[newn].c)p[++newn]=p[i],p[newn].cnt=1;
else p[newn].cnt++;
}
swap(n,newn);
solve(1,n);
memset(ans,0,sizeof(ans));
for(int i=1;i<=n;i++)ans[p[i].ans]+=p[i].cnt;
for(int i=1;i<=newn;i++)printf("%d\n",ans[i]);
return 0;
}

但是如果增加了修改怎么办?

这是时CDQ分治就变成了真·CDQ分治

我们同样可以把问题递归并且只考虑当前增层的状态

但是我们发现了修改这一神奇物质

所以我们先很自然不可抗力地把原问题按照时间排序,。。。其实就是不动

然后我们解决了时间的限制之后就可以在原问题上进行递归了

思路大概是这样的:

我们先将问题递归到左右子区间,分别统计之后我们只需要统计左区间修改对右区间查询的贡献(为什么没有右区间到左区间呢?),然后我们将左区间的修改和右区间的查询全部拿出来(感性理解一下),然后我们发现这个时候修改和查询又混在一起了,但是我们不用考虑时间关系只用考虑位置关系,所以我们就可以直接按照某一维的位置关系排一个序。。。

然后我们就发现我们将原问题成功的降维了

感觉贼优秀

HDU5126

本人博客

/*HDU5126 CDQ分治*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define N 500010
struct BIT{
int t[N];
void add(int x,int vl){for(;x<N;x+=x&(-x))t[x]+=vl;}
int query(int x){int ans=0;for(;x;x-=x&(-x))ans+=t[x];return ans;}
}T;
struct Que{
int x,y,z,id,typ,w;
Que(){}
Que(int _x,int _y,int _z,int _id,int _typ,int _w){
x=_x,y=_y,z=_z,id=_id,typ=_typ,w=_w;
}
}q1[N<<3],q2[N<<3],q3[N<<3],q4[N<<3];
int n,t,pre[N<<1],ans[N];
vector<int> v;
bool cmp1(Que a,Que b){
if(a.x!=b.x)return a.x<b.x;
return a.id<b.id;
}
bool cmp2(Que a,Que b){
if(a.y!=b.y)return a.y<b.y;
return a.id<b.id;
}
void solve2(int l,int r){
if(l>=r)return;
int mid=(l+r)>>1;
solve2(l,mid);
solve2(mid+1,r);
int lenl=0,lenr=0;
for(int i=l;i<=mid;i++)if(!q2[i].typ)q3[++lenl]=q2[i];
for(int i=mid+1;i<=r;i++)if(q2[i].typ)q4[++lenr]=q2[i];
sort(q3+1,q3+lenl+1,cmp2);
sort(q4+1,q4+lenr+1,cmp2);
int tl=1,tr=1;
while(tr<=lenr){
while(tl<=lenl&&q3[tl].y<=q4[tr].y)T.add(q3[tl].z,1),tl++;
ans[q4[tr].id]+=q4[tr].w*T.query(q4[tr].z);
tr++;
}
for(int i=1;i<tl;i++)T.add(q3[i].z,-1);
}
void solve1(int l,int r){//消除x维的影响
if(l>=r)return;
int mid=(l+r)>>1;
solve1(l,mid);
solve1(mid+1,r);
int newq=0;
for(int i=l;i<=mid;i++)if(!q1[i].typ)q2[++newq]=q1[i];
for(int i=mid+1;i<=r;i++)if(q1[i].typ)q2[++newq]=q1[i];
sort(q2+1,q2+newq+1,cmp1);
solve2(1,newq);
}
int main(){
//freopen("hdu5126.in","r",stdin);
scanf("%d",&t);
while(t--){
v.clear();
scanf("%d",&n);
int cnt=0,tot=0;
memset(ans,0,sizeof(ans));
for(int i=1;i<=n;i++){
int op;scanf("%d",&op);
if(op==1){
cnt++;
scanf("%d%d%d",&q1[cnt].x,&q1[cnt].y,&q1[cnt].z);
q1[cnt].id=i;q1[cnt].typ=0;
pre[++tot]=q1[cnt].z;
}else{
int x1,y1,z1,x2,y2,z2;
scanf("%d%d%d",&x1,&y1,&z1);
scanf("%d%d%d",&x2,&y2,&z2);
pre[++tot]=z1-1;
pre[++tot]=z2;
q1[++cnt]=Que(x2,y2,z2,i,1,1);
q1[++cnt]=Que(x1-1,y2,z2,i,1,-1);
q1[++cnt]=Que(x2,y1-1,z2,i,1,-1);
q1[++cnt]=Que(x2,y2,z1-1,i,1,-1);
q1[++cnt]=Que(x1-1,y1-1,z2,i,1,1);
q1[++cnt]=Que(x1-1,y2,z1-1,i,1,1);
q1[++cnt]=Que(x2,y1-1,z1-1,i,1,1);
q1[++cnt]=Que(x1-1,y1-1,z1-1,i,1,-1);
v.push_back(i);
}
}
sort(pre+1,pre+tot+1);
tot=unique(pre+1,pre+tot+1)-pre-1;
for(int i=1;i<=cnt;i++)q1[i].z=lower_bound(pre+1,pre+tot+1,q1[i].z)-pre;
solve1(1,cnt);//***cnt!=n
for(int i=0;i<v.size();i++)printf("%d\n",ans[v[i]]);
}
return 0;
}

CDQ分治--用时间降维的美丽算法的更多相关文章

  1. CH 4701 - 天使玩偶 - [CDQ分治]

    题目链接:传送门 关于CDQ分治(参考李煜东<算法竞赛进阶指南>): 对于一系列操作,其中的任何一个询问操作,其结果必然等价于:初始值 + 此前所有的修改操作产生的影响. 假设共有 $m$ ...

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

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

  3. 【BZOJ2141】排队(CDQ分治)

    [BZOJ2141]排队(CDQ分治) 题面 题面以及树套树做法见这里 题解 大部分树套树/主席树这类题目都可以用整体二分/CDQ分治来做. 这题考虑一下,在不考虑修改的情况下 贡献是如何产生的? 我 ...

  4. HDU5126 stars【CDQ分治】*

    HDU5126 stars Problem Description John loves to see the sky. A day has Q times. Each time John will ...

  5. HDU 6183 Color it cdq分治 + 线段树 + 状态压缩

    Color it Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 132768/132768 K (Java/Others) Pro ...

  6. BZOJ 2683: 简单题(CDQ 分治)

    题面 Time Limit: 50 Sec  Memory Limit: 128 MB Description 你有一个N*N的棋盘,每个格子内有一个整数,初始时的时候全部为0,现在需要维护两种操作: ...

  7. 点分治&cdq分治 总结

    游荡的孤高灵魂不需要羁绊之处. 洛谷题单 点分治 前置芝士 树的重心 树分治 例题略解 P3806 [模板]点分治1 板子题,先暴力找到整棵树的重心,然后先求出重心到各点的距离,进而算出他所在树的各个 ...

  8. 【算法学习】【洛谷】cdq分治 & P3810 三维偏序

    cdq是何许人也?请参看这篇:https://wenku.baidu.com/view/3b913556fd0a79563d1e7245.html. 在这篇论文中,cdq提出了对修改/询问型问题(Mo ...

  9. 算法复习——cdq分治

    题目: Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),又三个整数表示.现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量.定义一朵花A比另一朵花B要 ...

随机推荐

  1. Java循环跳转语句之 break

    生活中,我们经常会因为某些原因中断既定的任务安排.如在参加 10000 米长跑时,才跑了 500 米就由于体力不支,需要退出比赛.在 Java 中,我们可以使用 break 语句退出指定的循环,直接执 ...

  2. jQuery实际案例③——手风琴的效果

    如图,用最简单的方式实现手风琴的效果,核心,就是通过改变自身即鼠标移上去的那张图的width,与其他的width就可,但是需要用animate,先改变自己的width,再改变其他的.

  3. Sql字符串操作函数

    1.去空格函数 (1).LTRIM() 把字符串头部的空格去掉. (2).RTRIM() 把字符串尾部的空格去掉. 2.字符转换函数(1).ASCII()返回字符表达式最左端字符的ASCII 码值.在 ...

  4. 英语每日阅读---6、VOA慢速英语(翻译+字幕+讲解):性格沉静内向的人 能为社会创造更多价值

    英语每日阅读---6.VOA慢速英语(翻译+字幕+讲解):性格沉静内向的人 能为社会创造更多价值 一.总结 一句话总结: a.favor individual activities:Introvert ...

  5. C++(三十) — this 指针

    1.如何区分多个对象调用同一个类函数? 类外部访问类成员,必须用对象来调用.一个类的所有对象在调用的成员函数,都执行同一段代码,那成员函数如何区分属于哪个对象呢? 在对象调用成员函数时,除接收实参外, ...

  6. iptables疑问总结(一)

    1.关于-j 的return说明 1. 从一个CHAIN里可以jump到另一个CHAIN, jump到的那个CHAIN是子CHAIN.2. 从子CHAIN return后,回到触发jump的那条规则, ...

  7. 优先队列PriorityQueue实现 大小根堆 解决top k 问题

    转载:https://www.cnblogs.com/lifegoesonitself/p/3391741.html PriorityQueue是从JDK1.5开始提供的新的数据结构接口,它是一种基于 ...

  8. [Android]如何减小APK的大小

    能不引用的外部包就不用,删除没用的图片.xml,优化代码去掉没用的部分,能异步下载的资源就运行时从网络上下载.

  9. datagrid与DropDownList关联使用

    最近做一个页面需要用到这个两个控件,之前虽然看过,但是没有动手实践过.突然要做这么一个页面,并用上,真的有点着急.于是乎,网上疯狂找datagrid与DropDownList 的例子,找了很多很多,看 ...

  10. ubuntu安装amd/ati显卡驱动

    原网页: http://forum.ubuntu.org.cn/viewtopic.php?f=126&t=390372 整合了几个帖子,大概如此:用以下命令卸载所有驱动: 代码: sudo ...