离线算法——CDQ分治

  CDQ (SHY)显然是一个人的名字,陈丹琪(MM)(NOI2008金牌女选手)。

  • 从归并开始(这里并没有从逆序对开始,是想直接引入分治思想,而不是引入处理对象)

  一个很简单的归并排序:一个乱序的数列,每次将其折半,类似于线段树这样的数据结构,每个子区间先处理好,最后汇总到上一层。

其中层数不超过log(n)层,每次处理的复杂度是O(n)的,因此其复杂度为O(nlogn)。

  code:

void merge_sort(int l,int r)
{
if(l==r)return;
int mid=(l+r>>);
merge_sort(l,mid);merge_sort(mid+,r);
int i=l,j=mid+,k=l;
while(i<=mid&&j<=r)
{
if(a[i]<a[j])t[k++]=a[i++];
else t[k++]=a[j++];
}
while(i<=mid)t[k++]=a[i++];
while(j<=r)t[k++]=a[j++];
go(i,l,r)a[i]=t[i];
}
  •   简单应用(逆序对)

  逆序对就是求数列中满足i<j&&a[i]>a[j]的二元组(i,j)的对数。

  举个栗子(真好吃):1,4,3,8,4,3,8(val)

            1,2,3,4,5,6,7(pos)

  对于pos:(2,3),(2,6),(4,5),(4,6),(5,6)都是逆序对

  于是就我们可以由归并的性质:因为pos已经天然有序,因此我们只要看val就可以了。

  在merge时,如果右边的当前位置j比左边的位置i小,那么它一定比[i,mid]中的所有数都小,因此ans+=mid-i+1。

  •   从逆序对到二维偏序问题

  二维偏序问题其实就是把逆序对的pos打乱,且会重复,它可以理解为在平面直角坐标系中,有n个点(i,j),求每个点和(0,0)形成的矩形内有多少个点。

  双关键字排序后直接树状数组即可:

  例题:二维偏序

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define lowbit(x) (x&-x)
using namespace std;
const int N=;
struct star
{
int x,y;
}s[N];
long long tarr[N];
int n;
long long ans; bool cmp(star a,star b)
{
return a.x==b.x?a.y<b.y:a.x<b.x;
} void add(int pos,int val)
{
while(pos<=N)
{
tarr[pos]+=val;
pos+=lowbit(pos);
}
} int query(int pos)
{
int res=;
while(pos)
{
res+=tarr[pos];
pos-=lowbit(pos);
}return res;
} int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d%d",&s[i].x,&s[i].y);
}
sort(s+,s++n,cmp);
for(int i=;i<=n;i++)
{
ans+=query(s[i].y);
add(s[i].y,);
}
cout<<ans;
}
  •    从二维偏序到三维偏序

  板子:陌上花开

  有了之前的基础,三维偏序也很简单:

  我们按三关键字排序,(优先级a>b>c),对第二位进行归并排序,在merge时,对于左边的i和右边的j,如果第二维满足(bi<bj),则在树状数组中加入ci,直到存在某个bi不满足此关系,则j可以查询树状数组中所有ci<cj的三元组,由于之前对第一维的排序和对第二维的归并,前两维一定是满足条件的。这里有个去重还是挺烦的。

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define lowbit(x) (x&-x)
using namespace std;
const int N=;
const int M=;
struct node
{
int a,b,c,f,w;
}e[N],t[N];
int cnt;
int n,m;
int ans[N];
int tarr[M]; bool cmp(node x,node y)
{
return x.a==y.a?(x.b==y.b?x.c<y.c:x.b<y.b):x.a<y.a;
} void add(int pos,int val)
{
while(pos<=m)
{
tarr[pos]+=val;
pos+=lowbit(pos);
}
} int query(int pos)
{
int res=;
while(pos)
{
res+=tarr[pos];
pos-=lowbit(pos);
}return res;
} void CDQ(int l,int r)
{
if(l==r)return;
int mid=(l+r>>);
CDQ(l,mid);CDQ(mid+,r);
int i=l,j=mid+,k=l;
while(i<=mid&&j<=r)
{
if(e[i].b<=e[j].b)add(e[i].c,e[i].w),t[k++]=e[i++];
else e[j].f+=query(e[j].c),t[k++]=e[j++];
}
while(i<=mid)add(e[i].c,e[i].w),t[k++]=e[i++];
while(j<=r)e[j].f+=query(e[j].c),t[k++]=e[j++];
for(int i=l;i<=mid;i++)add(e[i].c,-e[i].w);
for(int i=l;i<=r;i++)e[i]=t[i];
} int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++)
scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c),e[i].w=;
sort(e+,e++n,cmp);
cnt=;
for(int i=;i<=n;i++)
{
if(e[i].a==e[cnt].a&&e[i].b==e[cnt].b&&e[i].c==e[cnt].c)
e[cnt].w++;
else e[++cnt]=e[i];
}
CDQ(,cnt);
for(int i=;i<=cnt;i++)ans[e[i].f+e[i].w-]+=e[i].w;
for(int i=;i<n;i++)printf("%d\n",ans[i]);
}
  •   应用:

  Mokia

  这是一个三维偏序问题,我们把时间当做第一维,x当做第二维,y当做第三维。

  按照处理三维偏序的思路,我们先按(t>x>y)排序,对x进行归并,对y进行树状数组。

  但是此题的查询比较烦:它查询的是矩阵前缀和。因此对每个查询,我们要处理四个区间的前缀和。这里我把一次询问拆成了四次询问。

  具体细节看code:

  

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define lowbit(x) (x&-x)
using namespace std;
const int W=;
const int Q=;
struct node
{
int id,x,y,t,sum;//id为时间,t为类型(add还是query),t=0,sum为添加的值,
//t=1,sum为返回值
}e[Q],t[Q];
int cnt;
int tarr[W]; inline int read()
{
int x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getchar();}
while(ch>=''&&ch<=''){x=x*+ch-'';ch=getchar();}
return x*f;
} int w; bool cmp(node a,node b)
{
return a.id==b.id?(a.x==b.x?a.y<b.y:a.x<b.x):a.id<b.id;
} bool cmp2(node a,node b)
{
return a.id<b.id;
} void add(int pos,int val)
{
while(pos<=W)
{
tarr[pos]+=val;
pos+=lowbit(pos);
}
} int query(int pos)
{
int res=;
while(pos)
{
res+=tarr[pos];
pos-=lowbit(pos);
}return res;
} void CDQ(int l,int r)//CDQ分治和归并板子其实没啥本质区别
{
if(l==r)return;
int mid=(l+r>>);
CDQ(l,mid);CDQ(mid+,r);
int i=l,j=mid+,k=l;//基本操作
while(i<=mid&&j<=r)
{
if(e[i].x<=e[j].x)//对第二维进行归并,如果满足条件,把第三维的贡献放到树状数组里
{
if(e[i].t==)add(e[i].y,e[i].sum);//此时t需要为0
t[k++]=e[i++];
}
else
{
if(e[j].t==)e[j].sum+=query(e[j].y);//如果已经没有满足条件的x了,我们就进行统计
t[k++]=e[j++];//此时t需要为1
}
}
while(i<=mid)
{
if(e[i].t==)
add(e[i].y,e[i].sum);
t[k++]=e[i++];
}
while(j<=r)
{
if(e[j].t==)
e[j].sum+=query(e[j].y);
t[k++]=e[j++];
}//统计剩下的
for(int i=l;i<=mid;i++)if(e[i].t==)add(e[i].y,-e[i].sum);//必须要清空树状数组
for(int i=l;i<=r;i++)
e[i]=t[i];
} int main()
{
while()
{
int cid=read();
if(cid==)w=read();
if(cid==)
{
int x=read()+,y=read()+,z=read();
e[++cnt]=(node){cnt,x,y,,z}; //结构体直接读取
}
if(cid==)
{
int i=read(),j=read(),x=read()+,y=read()+;//x,y可能为0,树状数组会爆
e[++cnt]=(node){cnt,i,j,,};//因此它们都要加一,对结果无影响
e[++cnt]=(node){cnt,i,y,,};
e[++cnt]=(node){cnt,x,j,,};
e[++cnt]=(node){cnt,x,y,,};//一个询问拆成四个询问
}
if(cid==)break;
}
sort(e+,e++cnt,cmp);//对第一维排序
CDQ(,cnt);
sort(e+,e++cnt,cmp2);//查询之前一定要按时间排好序,因为已经按第二维归并了一遍
for(int i=;i<=cnt;i++)
{
int ans=;
if(e[i].t==)
{
int a=e[i].sum,b=e[i+].sum,c=e[i+].sum,d=e[i+].sum;
ans=a-b-c+d;printf("%d\n",ans);//遇到查询,4个合并起来就是最终答案
i+=;
}
}
}

  二维偏序很好懂,三维偏序太难画,所以这里就不放图了

  完美撒花✿✿ヽ(°▽°)ノ✿

  ——Thranduil

CDQ分治(学习笔记)的更多相关文章

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

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

  2. CDQ分治学习笔记

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

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

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

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

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

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

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

  6. CDQ分治学习思考

    先挂上个大佬讲解,sunyutian1998学长给我推荐的mlystdcall大佬的[教程]简易CDQ分治教程&学习笔记 还有个B站小姐姐讲解的概念https://www.bilibili.c ...

  7. cdq分治学习

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

  8. [Updating]点分治学习笔记

    Upd \(2020/2/15\),又补了一题 LuoguP2664 树上游戏 \(2020/2/14\),补了一道例题 LuoguP3085 [USACO13OPEN]阴和阳Yin and Yang ...

  9. 点分治&&动态点分治学习笔记

    突然发现网上关于点分和动态点分的教程好像很少……蒟蒻开篇blog记录一下吧……因为这是个大傻逼,可能有很多地方写错,欢迎在下面提出 参考文献:https://www.cnblogs.com/LadyL ...

  10. 学习笔记 | CDQ分治

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

随机推荐

  1. maven突然报大量package does not exist(包不存在)问题

    遇到个问题,不知道原因,虽然已解决,但是扔不知道为什么,希望有大神帮忙解答下~~~不胜感激~~~ 国庆假期回来后,小伙伴发布测试服务器版本忽然发现报错,我咨询IT管理组近期并没有对服务器配置和权限做调 ...

  2. 直通BAT面试题库锦集

    01 python面试题(汇总) 02 面向对象 03 网络和并发编程 04 模块 05 设计模式 06 前端 07 Django框架 08 Flask 09 tornado 10 DB

  3. wsgi相关的

    目录 web 本质 http协议 请求方式 响应状态码 请求与响应文本格式 目录 web 本质   本质就是浏览器和服务器进行通信, http协议   也叫超文本传输协议(英文:HyperText T ...

  4. 深入理解Netty框架

    前言 本文讨论的主题是Netty框架,本着3W原则 (What 是什么?->Why 为什么?->How 如何做?)来一步步探究Netty原理和本质以及运用场景. 了解基本名词 1.BIO. ...

  5. BZOJ - 2783 树

    数列 提交文件:sequence.pas/c/cpp 输入文件: sequence.in 输出文件: sequence.out 问题描述: 把一个正整数分成一列连续的正整数之和.这个数列必须包含至少两 ...

  6. C#刷遍Leetcode面试题系列连载(4) No.633 - 平方数之和

    上篇文章中一道数学问题 - 自除数,今天我们接着分析 LeetCode 中的另一道数学题吧~ 今天要给大家分析的面试题是 LeetCode 上第 633 号问题, Leetcode 633 - 平方数 ...

  7. App元素定位

    1.元素定位(采用Appium-desktop自带的工具) 1.1将初始化参数复制进去校验json格式正确且保存后,点击start session 初始化参数来源如下: # 定义启动设备需要的参数 d ...

  8. 面试必备:高频算法题终章「图文解析 + 范例代码」之 矩阵 二进制 + 位运算 + LRU 合集

    Attention 秋招接近尾声,我总结了 牛客.WanAndroid 上,有关笔试面经的帖子中出现的算法题,结合往年考题写了这一系列文章,所有文章均与 LeetCode 进行核对.测试.欢迎食用 本 ...

  9. HTML5远程工具

    因为有从网页直接远程其他windows电脑的需求,于是通过网上搜索找到下面几个解决方案,分享一下: 1.windows的远程桌面web连接tsweb 下载地址https://www.microsoft ...

  10. Python3的编码整理总结

    python3在内存中是用unicode编码方式存储的,所以不能直接储存和传输,要转化为其他编码进行储存和传输. 字符串通过编码转换成字节码,字节码通过解码成为字符串 encode:str --> ...