【算法学习】【洛谷】cdq分治 & P3810 三维偏序
cdq是何许人也?请参看这篇:https://wenku.baidu.com/view/3b913556fd0a79563d1e7245.html。
在这篇论文中,cdq提出了对修改/询问型问题(Modify-Query问题)的分治做法,下面来具体讨论一下:
我们将修改/询问看作在时间轴上的一系列元素,把修改和询问统称为“操作”,并用记号\([l,r]\)表示第\(l\)个操作到第\(r\)个操作的序列。
在时间轴上进行的操作,众所周知有这样的特性:时间早的会影响时间晚的,而反过来不会,这就是cdq分治的本质:用前面的修改更新后面的答案。
假设我们要计算一段长为\(m\)的区间\([l,r]\)的答案,考虑计算\([l,mid]\)和\([mid+1,r]\),最终将两段区间合并,其中\(mid=\left\lfloor\frac{l+r}{2}\right\rfloor\)。
这里,\([l,r]\)的答案,指的就是,单纯的\([l,r]\)的答案。不考虑更早的操作对\([l,r]\)的影响。
这样的做法是否似曾相识?典型的分治—合并的思路,就出现在最稳定的排序方法——归并排序上。
其时间复杂度为\(T(1)=\Theta(1), T(n)=2T(\frac{n}{2})+\Theta(n)=\Theta(n\,log\,n)\),其中合并区间的复杂度为\(\Theta(n)\)。
提出这个例子只是为了回忆起对分治感觉和思路。
看一道例题,方便讲解:(这题就是论文中的题目)
给你\(q\)个操作:①插入一个点\((x,y)\),②询问当前所有的点中,满足\(x_i\leq x,y_i\leq y\)的点\(x_i,y_i\)的个数(\(1\leq q,x,y\leq 100000\))。
我们采用cdq分治的方法,将这些操作按照时间顺序分治。
假设我们要获取\([l,r]\)的答案,而我们已经对\([l,mid],[mid+1,r]\)进行了递归处理,这时我们需要处理出完整的答案。
就像归并排序的过程一样,左边的区间和右边的区间都自己处理好自己的答案了,可是整个区间的答案仍没有完全算出,这是因为没有考虑左边区间对右边区间的影响。
在本题中,“影响”就是\([l,mid]\)中插入的点,会被统计到\([mid+1,r]\)中去。
这时,发现(可以造成影响的)左侧只有修改,右侧只有询问,考虑将修改和询问都按照他们的\(x_i\)为第一关键字,\(y_i\)为第二关键字,修改优先,查询后置为第三关键字排序,放到树状数组里去处理。
因为不需要考虑时间的顺序了,所以这很容易就能做到。
时间复杂度统计:\(T(1)=\Theta(1),T(n)=2T(\frac{n}{2})+\Theta(n\,log\,y)=\Theta(n\,log\,n\,log\,y)\)。
分析:使用cdq分治,我们将有时间顺序的操作转换成修改在前,询问在后的操作,相当于把问题简单化,“去时间化”了。
更深入地说,对于这样一个问题:初始时有一些三维点\((x_i,y_i,z_i)\),只需要查询满足\(x_i\leq x,y_i\leq y,z_i\leq z\)的点\(x_i,y_i,z_i\)的个数。
这看似和上一题相比,多了一维,但是若我们将所有的点和查询按照\(z_i,x_i,y_i,\)修改-查询依次为关键字排序,再把\(z\)坐标扔掉,这不就把空间上的维度转化成时间轴上的顺序了吗?所以这题本质上和上一题相同,可以用同样的算法解决。
这题就是洛谷的P3810 三维偏序,我的代码如下:
#include<cstdio>
#include<algorithm>
struct ww{int x,y,z,w,I;}a[],tmp[];
inline bool cmp1(ww p1,ww p2){return p1.x==p2.x?(p1.y==p2.y?p1.z<p2.z:p1.y<p2.y):p1.x<p2.x;}
int n,nn,k,bit[],ans[],Ans[];
inline void Ins(int i,int x){for(;i<=k;bit[i]+=x,i+=i&-i);}
inline int Qur(int i){int sum=;for(;i;sum+=bit[i],i-=i&-i);return sum;}
void cdq(int l,int r){
if(l==r) return;
int mid=(l+r)>>;
cdq(l,mid); cdq(mid+,r);
for(int i=l,lf=l,rt=mid+;lf<=mid||rt<=r;++i){
if((lf<=mid&&rt>r)||(lf<=mid&&rt<=r&&a[lf].y<=a[rt].y)) Ins(a[lf].z,a[lf].w), tmp[i]=a[lf], ++lf;
else Ans[a[rt].I]+=Qur(a[rt].z), tmp[i]=a[rt], ++rt;
}
for(int i=l;i<=mid;++i) Ins(a[i].z,-a[i].w);
for(int i=l;i<=r;++i) a[i]=tmp[i];
}
int main(){
scanf("%d%d",&n,&k);
for(int i=;i<=n;++i) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
std::sort(a+,a+n+,cmp1);
for(int i=;i<=n;++i) if(a[i].x!=a[i-].x||a[i].y!=a[i-].y||a[i].z!=a[i-].z) a[++nn]=a[i], a[nn].w=, a[nn].I=nn; else ++a[nn].w;
cdq(,nn);
for(int i=;i<=nn;++i) ans[Ans[a[i].I]+a[i].w-]+=a[i].w;
for(int i=;i<n;++i) printf("%d\n",ans[i]);
return ;
}
偏序其实就是两个元素的值都对应地大(小)(等)于的关系,有时间顺序的问题也可以通过记时间标记转化成偏序问题,上面的两道题就是偏序问题。
数据结构(树状数组,线段树,平衡树等)通常可以处理较低维的偏序问题,而cdq分治则是处理偏序问题的利器。
把偏序问题中的某一维排序变为时间轴,在这时间轴之上分治处理,在合并时,就巧妙地将修改和查询分离开来,成为两个不相交的部分,没有了时间顺序的困扰,解决自然变得容易。
若是更高维的偏序,转化后仍不能处理的,我们可以将转化后的(没有时间顺序的)操作序列再排序,分离出时间轴来,对之下的在进行一次cdq分治,即cdq套cdq。
这样做,思维难度、代码复杂度以及调试难度都有所提升,但cdq分治本质上熟练了就很好写了,应该要多多益善地练习相关题目。
运用cdq的注意事项:所有的修改和查询都必须是已知的,即cdq是离线算法,对于强制在线的题目,就要另寻他法了。
cdq分治的应用不止于此,还有许多问题,运用相似的思想——分治,也能获得简便的解决,在此就不一一列举了。
更多例题:P4093 [HEOI2016/TJOI2016]序列
【算法学习】【洛谷】cdq分治 & P3810 三维偏序的更多相关文章
- 并不对劲的cdq分治解三维偏序
为了反驳隔壁很对劲的太刀流,并不对劲的片手流决定与之针锋相对,先一步发表cdq分治解三维偏序. 很对劲的太刀流在这里-> 参照一.二维偏序的方法,会发现一位偏序就是直接排序,可以看成通过排序使 ...
- 洛谷P3810-陌上开花(三维偏序, CDQ, 树状数组)
链接: https://www.luogu.org/problem/P3810#submit 题意: 一个元素三个属性, x, y, z, 给定求f(b) = {ax <= bx, ay < ...
- cdq分治解决三维偏序
问题背景 在三维坐标系中有n个点,坐标为(xi,yi,zi). 定义一个点A比一个点B小,当且仅当xA<=xB,yA<=yB,zA<=zB.问对于每个点,有多少个点比它小.(n< ...
- hdu5618(cdq分治求三维偏序)
题意:给n(n<=100000)个点,坐标为(xi,yi,zi)(1<=xi,yi,zi<=100000),定义一个点A比一个点B小,当且仅当xA<=xB,yA<=yB, ...
- SPOJ:Another Longest Increasing Subsequence Problem(CDQ分治求三维偏序)
Given a sequence of N pairs of integers, find the length of the longest increasing subsequence of it ...
- BZOJ 3262: 陌上花开 (cdq分治,三维偏序)
#include <iostream> #include <stdio.h> #include <algorithm> using namespace std; c ...
- HDU4742 CDQ分治,三维LIS
HDU4742 CDQ分治,三维LIS 传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4742 题意: 每个球都有三个属性值x,y,z,要求最长的lis的 ...
- [bzoj] 3263 陌上花开 洛谷 P3810 三维偏序|| CDQ分治 && CDQ分治讲解
原题 定义一个点比另一个点大为当且仅当这个点的三个值分别大于等于另一个点的三个值.每比一个点大就为加一等级,求每个等级的点的数量. 显然的三维偏序问题,CDQ的板子题. CDQ分治: CDQ分治是一种 ...
- 线段树分治初步学习&洛谷P5227[AHOI2013]连通图
线段树分治 其实思想说起来是比较简单的,我们把这个题里的所有操作(比如连边删边查询balabala)全部拍到一棵线段树上,然后对着整棵树dfs一下求解答案,顺便把操作做一下,回溯的时候撤销一下即可.虽 ...
随机推荐
- UVAlive6439_Pasti Pas!
题目是说给你一个字符串,现在要你用一些特殊的符号代替这个字符串中某一些子串,使得被替换后的串是一个回文串. 现在要你求替换后的字符串的最大的可能的长度. 其实这个题目没有什么固定的算法哦,我直接暴力就 ...
- iOS 简单获取当前地理坐标
iOS 获取当前地理坐标 iOS获取当前地理坐标,很简单几句代码,但是如果刚开始不懂,做起来也会也会出现一些问题. 1.导入定位需要用到的库:CoreLocation.framwork ...
- JVM类加载机制详解(二)类加载器与双亲委派模型
在上一篇JVM类加载机制详解(一)JVM类加载过程中说到,类加载机制的第一个阶段加载做的工作有: 1.通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件).而获取的方式,可 ...
- 利用VRID/VMAC实现更安全的netscaler HA故障切换
利用VRID/VMAC实现更安全的netscaler HA故障切换 virtual MAC在故障切换(failover)中的作用. 在一个HA模式中,首要节点(primary node)拥有所有 ...
- [洛谷P4340][SHOI2016]随机序列
题目大意:有$n(n\leqslant10^5)$个数,每两个数之间可以加入$+-\times$三种符号,$q(q\leqslant10^5)$次询问,每次询问修改一个数后,所有表达式可能的值的和 题 ...
- 学习Spring Boot:(三)配置文件
前言 Spring Boot使用习惯优于配置(项目中存在大量的配置,此外还内置了一个习惯性的配置,让你无需手动进行配置)的理念让你的项目快速运行起来. 正文 使用配置文件注入属性 Spring Boo ...
- 【省选水题集Day1】一起来AK水题吧! 题目(更新到B)
题解:http://www.cnblogs.com/ljc20020730/p/6937954.html 水题A: [AHOI2001]质数和分解 题目网址: https://www.luogu.or ...
- 【NOI 2018】归程(Kruskal重构树)
题面在这里就不放了. 同步赛在做这个题的时候,心里有点纠结,很容易想到离线的做法,将边和询问一起按水位线排序,模拟水位下降,维护当前的各个联通块中距离$1$最近的距离,每次遇到询问时输出所在联通块的信 ...
- 洛谷 P1850 换教室 解题报告
P1850 换教室 题目描述 对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程. 在可以选择的课程中,有\(2n\)节课程安排在\(n\)个时间段上.在第\(i(1≤i≤n) ...
- JAVA中的File.separate(跨平台路径)
转: JAVA中的File.separate(跨平台路径) 2016年03月27日 23:33:50 才不是本人 阅读数:1952 在Windows下的路径分隔符和Linux下的路径分隔符是不一样 ...