离线算法——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. Mysql数据类型TINYINT(1)与BOOLEAN踩坑记

    熟悉Mysql的同学应该都知道,Mysql查询的boolean结果将输出为0或者1. 比如: ; 其输出结果为1. 查阅mysql官方文档仅找到如下描述: 11.10 Using Data Types ...

  2. 使用lombok中的log

    idea中安装lombok插件 引入lombok依赖 <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> ...

  3. slf4j+logback&logback.xml

    添加maven依赖 <dependencies> <!--https://mvnrepository.com/artifact/org.slf4j/slf4j-api --> ...

  4. .netCore+Vue 搭建的简捷开发框架 (4)--NetCore 基础 -2

    上节中,我们初步的介绍了一下NetCore的一些基础知识,为了控制篇幅(其实也是因为偷懒),我将NetCore 基础分为两部分来写. 0.WebAPI 项目的建立 1..NetCore 项目执行(加载 ...

  5. Arouter核心思路和源码详解

    前言 阅读本文之前,建议读者: 对Arouter的使用有一定的了解. 对Apt技术有所了解. Arouter是一款Alibaba出品的优秀的路由框架,本文不对其进行全面的分析,只对其最重要的功能进行源 ...

  6. 玩转ADB命令(ADB命令使用大全)转载

    ADB是什么 Adb的全称为Android Debug Bridge:android调试桥梁,下图为Android官方对adb的介绍:可以看出,Android的初衷是用adb这样的一个工具来协助开发人 ...

  7. RAID5 配置,3块磁盘,2快备份

    1. 在虚拟机中再添加5块硬盘: 2. 用fdisk -l 可以查看当前虚拟机磁盘情况. 3. 使用mdadm命令创建RAID5,名称为”/dev/md0″. -C代表创建操作,-v显示创建过程,-n ...

  8. top命令之性能分析

    top命令详解 当前时间20:27:12 当前系统运行时间3:18秒    1个用户   系统负载平均长度为0.00,0.00,0.00(分别为1分钟.5分钟.15分钟前到现在的平均值) 第二行为进程 ...

  9. VPS虚拟专用服务器

    目录   0x00 VPS服务器概述 0x01 VPS工作原理 0x02 VPS用途 0x03 VPS优势 0x04 VPS特点 0x00 VPS服务器概述 VPS服务器(虚拟专用服务器)(" ...

  10. 研究了3天,终于将 Shader 移植到 Cocos Creator 2.2.0 上了!

    预览 扫光特效-Fluxay2 马赛克像素特效-Mosaic 过渡效果-Transfer Shawn 花了3天时间,研究了Cocos Creator 2.2.0 的 Effect 语法,终于在1024 ...