离线算法——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. Bitmap简介

    1.  BitMap Bit-map的基本思想就是用一个bit位来标记某个元素对应的Value,而Key即是该元素.由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省.(PS:划重点 ...

  2. MySQL学习(三)MySQL锁与事务

    本章我们着重讨论MySQL锁机制的特点,常见的锁问题,以及解决MySQL锁问题的一些方法或建议. 一.MySQL锁概述 相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支 ...

  3. Python学习笔记五(读取提取写入文件)

    #Python打开读取一个文件内容,然后写入一个新的文件中,并对某些字段进行提取,写入新的字段的脚本,与大家共同学习. import os import re def get_filelist(dir ...

  4. Django-debug-toolbar(调试使用)

    Django-debug-toolbar django-debug-toolbar 是一组可配置的面板,可显示有关当前请求/响应的各种调试信息,并在单击时显示有关面板内容的更多详细信息. https: ...

  5. 利用git工具将自己的代码文件上传到Github

    GitHub 是一个面向开源及私有软件项目的托管平台,作为开源代码库以及版本控制系统,Github拥有超过900万开发者用户.随着越来越多的应用程序转移到了云上,Github已经成为了管理软件开发以及 ...

  6. “selenium.common.exceptions.SessionNotCreatedException: Message: Unable to find a matching set of capabilities“解决办法

    问题: 原因:firefox浏览器版本和浏览器驱动版本不匹配 解决办法:卸载高版本浏览器,安装低版本浏览器 下载地址:http://ftp.mozilla.org/pub/firefox/releas ...

  7. 如何在 Creator3D 中切换模型贴图,超级简单!

    效果预览 前两天有伙伴在 QQ 上询问,如何在 Creator 3D 中切换模型贴图.Shawn 之前也没尝试过,不过根据之前 Cocos Creator 的经验以及这几天对 Creator 3D 的 ...

  8. 浅谈K-means聚类算法

    K-means算法的起源 1967年,James MacQueen在他的论文<用于多变量观测分类和分析的一些方法>中首次提出 “K-means”这一术语.1957年,贝尔实验室也将标准算法 ...

  9. 使用zepto中animate报错“Uncaught TypeError: this.bind is not a function”的解决办法

    在使用zepto时,我先引入zepto.min.js,然后引入fx.js,但是在使用animate函数时,控制台却报如下错误: Uncaught TypeError: this.bind is not ...

  10. 详解JavaScript调用栈、尾递归和手动优化

    调用栈(Call Stack) 调用栈(Call Stack)是一个基本的计算机概念,这里引入一个概念:栈帧. 栈帧是指为一个函数调用单独分配的那部分栈空间. 当运行的程序从当前函数调用另外一个函数时 ...