CDQ分治入门
前言
\(CDQ\)分治是一个神奇的算法。
它有着广泛的用途,甚至在某些题目中还能取代\(KD-Tree\)、树套树等恶心的数据结构成为正解,而且常数还小得多。
不过它也有一定的缺点,如必须离线操作,遇到强制在线的题目还是老老实实打树套树吧... ...
核心思想
\(CDQ\)分治的核心思想真的是非常简单,也就是分与治二字(其实所有分治算法都是这样)。
- 分: 与常见的二分一样,将\([l,r]\)区间内的问题分成两个区间\([l,mid]\)和\([mid+1,r]\)解决。
- 治: \(CDQ\)分治中的治这一部分就十分玄学了,它的思想是利用左区间来求解右区间,这与普通的分治就大不一样了。
这样讲毕竟还是十分抽象,让我们来借助一道经典例题,来粗略地见识一下\(CDQ\)分治的神奇所在。
经典例题:【BZOJ3262】陌上花开
这道题目大致题意就是要你求三维偏序。
关于二维偏序
谈到三维偏序,我们可能首先会想到二维偏序。
或许有些人不知道什么是二维偏序,但它的另一个名称——逆序对你总知道吧。
二维偏序一般可以用树状数组或归并排序来解决。
关于用树状数组,其实我们接下来还要用到。
而对于归并排序,可以发现它其实也是借助了左区间来求解右区间,或许也能算作一个比较\(Simple\)的\(CDQ\)分治?(大雾)
好了,关于逆序对我们就扯到这里,下面我们来看看如何用\(CDQ\)分治求解三维偏序。
如何求解三维偏序
- 对于第一维
- 首先第一步是将数据按照第一维\(x\)进行排序。
- 这样就能保证第一维是有序的了。
- 对于第二维
- 接下来,在每一次处理完两个子区间的答案后(注意,一定要先处理子区间,因为接下来的排序会打乱元素的顺序),我们再将这两个子区间分别按照第二维\(y\)排序。
- 此时,我们依然可以保证,左区间内每个元素的第一维始终小于右区间内每个元素的第一维。(这应该是显然的吧)
- 对于第三维
- 我们可以用\(i\)和\(j\)分别记录右区间和左区间当前处理到的点。
- 对于每一个\(y_j\le y_i\)的\(j\),我们可以将其第三维\(z\)加入树状数组。
- 由于两个区间经过排序,\(y\)的大小是递增的,所以\(j\)的大小也是递增的,这样就能稳定时间复杂度。
- 现在,我们已经保证在树状数组中的所有元素,它的前两维皆\(\le\)当前\(i\)的前两维。因此,我们只要求出有多少个\(z\le z_i\)即可,用树状数组可以快速做到这一点。
- 这样一来,就能计算出\(i\)的三维偏序数量了。
大致就是这样一个过程,一次没有看明白的可以再多看几遍理解一下。
最后注意一个细节:千万记得清空树状数组!而且千万记得不要直接\(memset\)!
代码
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1))
#define N 100000
using namespace std;
int n,m,nn;
struct value
{
int x,y,z,v,tot;
inline friend bool operator == (value x,value y) {return !(x.x^y.x||x.y^y.y||x.z^y.z);}
}s[N+5];
class FIO
{
private:
#define Fsize 100000
#define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
#define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
public:
FIO() {FinNow=FinEnd=Fin;}
inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
inline void read_char(char &x) {while(isspace(x=tc()));}
inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
inline void write_char(char x) {pc(x);}
inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
inline bool cmp_x(value x,value y) {return x.x^y.x?x.x<y.x:(x.y^y.y?x.y<y.y:x.z<y.z);}//按第一维排序
inline bool cmp_y(value x,value y) {return x.y^y.y?x.y<y.y:x.z<y.z;}//按第二维排序
class Class_CDQ//CDQ分治
{
private:
int ans[N+5];//最后统计答案
class Class_BIT//树状数组
{
private:
#define M 200000
#define lowbit(x) ((x)&-(x))
int data[M+5];
public:
inline void Add(int x,int v) {while(x<=m) data[x]+=v,x+=lowbit(x);}//插入元素
inline int Query(int x,int ans=0) {while(x) ans+=data[x],x-=lowbit(x);return ans;}//询问≤x的数的和
}BIT;
public:
inline void Solve(int l,int r)//求解l到r这段区间内的答案
{
if(l>=r) return;
register int mid=l+r>>1,i,j=l;
Solve(l,mid),Solve(mid+1,r),sort(s+l,s+mid+1,cmp_y),sort(s+mid+1,s+r+1,cmp_y);//切记先求解子区间,然后再排序,排序之后依然能保证右区间第一维大于左区间第一维
for(i=mid+1;i<=r;++i)
{
while(j<=mid&&s[j].y<=s[i].y) BIT.Add(s[j].z,s[j].v),++j;//对于每一个y[j]≤y[i]的j,将z[j]插入树状数组
s[i].tot+=BIT.Query(s[i].z);//求出树状数组中≤z[i]的所有元素之和,从而更新i的三维偏序个数
}
for(i=l;i<j;++i) BIT.Add(s[i].z,-s[i].v);//切记要这样清空树状数组,memset会T飞(亲身实践)
}
inline void PrintAns()
{
register int i;
for(i=1;i<=n;++i) ans[s[i].tot+s[i].v-1]+=s[i].v;//统计答案
for(i=0;i<nn;++i) F.write(ans[i]),F.write_char('\n');//输出
}
}CDQ;
int main()
{
register int i;
for(F.read(nn),F.read(m),i=1;i<=nn;++i) F.read(s[i].x),F.read(s[i].y),F.read(s[i].z),s[i].v=1;
for(sort(s+1,s+nn+1,cmp_x),i=1;i<=nn;++i) n&&s[n]==s[i]?++s[n].v:(s[++n]=s[i],0);//按照第一维排序,然后去重,从而提高时间效率
return CDQ.Solve(1,n),CDQ.PrintAns(),F.end(),0;//用CDQ分治求解
}
后记
关于\(CDQ\)分治求解三维偏序,还有一道比较好的题目:【洛谷3157】[CQOI2011] 动态逆序对,可以去做一做。
(这道题卡树套树,我的 线段树套\(Treap\) 只得了\(80\)分,于是为做这题来学了\(CDQ\)分治)
CDQ分治入门的更多相关文章
- CDQ分治入门 + 例题 Arnooks's Defensive Line [Uva live 5871]
CDQ分治入门 简介 CDQ分治是一种特别的分治方法,它由CDQ(陈丹琦)神犇于09国家集训队作业中首次提出,因此得名.CDQ分治属于分治的一种.它一般只能处理非强制在线的问题,除此之外这个算法作为某 ...
- COGS 577 蝗灾 [CDQ分治入门题]
题目链接 昨天mhr神犇,讲分治时的CDQ分治的入门题. 题意: 你又一个w*w正方形的田地. 初始时没有蝗虫. 给你两个操作: 1. 1 x y z: (x,y)这个位置多了z只蝗虫. 2. 2 x ...
- cdq分治入门学习 cogs 1752 Mokia nwerc 2015-2016 G 二维偏序
/* CDQ分治的对象是时间. 即对于一个时间段[L, R],我们取mid = (L + R) / 2. 分治的每层只考虑mid之前的修改对mid之后的查询的贡献,然后递归到[L,mid],(mid, ...
- cdq分治入门and持续学习orz
感觉cdq分治是一个很有趣的算法 能将很多需要套数据结构的题通过离线来做 目前的一些微小的理解 在一般情况下 就像求三维偏序xyz 就可以先对x排序 然后分治 1 cdq_x(L,M) ; 2 提取出 ...
- caioj1097: [视频]树状数组1(快速求和计算) cdq分治入门
这题虽然是个树状数组,但是也可以用cdq分治做啊~~,这个就是一个浅显的二维偏序的应用? cdq分治和普通的分治有什么区别? 举个栗子:有4个小朋友,你请他们吃饭,假如你分治搞,就会分成很多子问题—— ...
- bzoj3262陌上花开 cdq分治入门题
Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),又三个整数表示.现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量.定义一朵花A比另一朵花B要美丽,当 ...
- 【学术篇】bzoj3262 陌上花开. cdq分治入门
花儿们已经很累了-- 无论是花形.颜色.还是气味, 都不是为了给人们摆出来欣赏的, 更不是为了当做出题的素材的, 她们并不想自己这些属性被没有生命的数字量化, 并不想和其它的花攀比, 并无意分出个三六 ...
- cdq分治入门--BZOJ1492: [NOI2007]货币兑换Cash
n<=100000天,一开始有s块钱,每天股票A价格ai,B价格bi,每天可以做的事情:卖出股票:按A:B=RTi的比例买入股票.问最后的最大收益.股票可以为浮点数,答案保留三位. 用脚指头想想 ...
- cdq分治入门--BZOJ3262: 陌上花开
n<=100000个人,每个人三个属性Ai,Bi,Ci,一个人i的等级为Ai>=Aj,Bi>=Bj,Ci>=Cj的人数,求每个等级有多少人. 裸的三维偏序.按照常规思路,一维排 ...
随机推荐
- Educational Codeforces Round 52F(树形DP,VECTOR)
#include<bits/stdc++.h>using namespace std;int n,k;vector<int>son[1000007];int dp[100000 ...
- XML之DTD
前言 上篇我们知道了,XML的应用范围还是很广的,那么无规矩不成方圆,如果我们每个人写XML的风格不一致,那么读起来是不是很费劲呢?所以格式良好的XML就渐渐的被我们所需要. 内容 格式良好指:拥有正 ...
- 括号序列(区间dp)
括号序列(区间dp) 输入一个长度不超过100的,由"(",")","[",")"组成的序列,请添加尽量少的括号,得到一 ...
- hdu 2147 kiki's game(巴什博弈)
kiki's game HDU - 2147 题意:一个n*m的表格,起始位置为右上角,目标位置为左下角,甲先开始走,走的规则是可以向左,向下或者向左下(对顶的)走一格.谁先走到目标位置谁就胜利.在甲 ...
- Jmeter常用线程组设置及场景运行时间计算
Jmeter中通过线程组来模拟大用户并发场景,今天主要介绍三个常用的线程组,帮助我们设计更加完善的测试场景,另外介绍下场景执行时间如何计算. 一.Thread Group 取样器错误后要执行的动作 ...
- JS精粹知识点和我的解释
函数若是不return则默认返回undefined const value=()=>{ let a=3; } let value_test; console.log(value(),value_ ...
- 旅行青蛙分析(Android篇)
近期旅行青蛙这款游戏非常的火热,周围的朋友.家人都养了一只小青蛙.看到网上有人说这款游戏可以直接逆向编译,没有加密:所以在搜索相关资料后花了一些时间进行逆向分析与修改.这篇文章里,我将介绍如何获取稀有 ...
- 分析师分析业务维度,(个人制作分析思维导图Xmind)
个人在咨询公司做过分析师(分析师必须懂运营),该咨询公司主要针对电商,零售 结合公司的搭建的CRM系统及报表体系,列了个分析师分析维度,搭建公司自己的BI系统 个人经验:分析师的分析思维可以多看看艾瑞 ...
- 使用shell巧妙高效的批量删除历史文件或目录
背景:有实时产生的数据按小时分文件保存,如“/data/2013/09/18/14.txt”.现需要保留30天的最新数据,而删除所有其它的历史数据.注意“保留30天的最新数据”,可能不是连续的30天, ...
- bootstrap multiselect的使用 多选下拉菜单
官网网址: http://davidstutz.de/bootstrap-multiselect/