P4169-CDQ分治/K-D tree(三维偏序)-天使玩偶
P4169-CDQ分治/K-D tree(三维偏序)-天使玩偶
这是一篇两种做法都有的题解
题外话
我写吐了……
本着不看题解的原则,没写(不会)K-D tree,就写了个cdq分治的做法。下面是我的写题步骤:
想着树状数组维护不了区间最值,于是写了线段树,因为一个**的错误调了几个小时;
cdq只写了两个方向。显然是错的,因为没考虑修改。所以挂了;
加上另外两个方向,正确性终于ok,兴高采烈地交上去然后TLE;
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<cmath>
using namespace std;
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read(){
int w=0,x=0;char c=getchar();
while(!isdigit(c))w|=c=='-',c=getchar();
while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
return w?-x:x;
}
void print(long long x) {
if(x>9) print(x/10);
*O++=x%10+'0';
}
namespace star
{
const int maxn=6e5+10,INF=0x3f3f3f3f;
int n,m,ans[maxn],LN=0,RN=1000006;
struct query{
int x,y,id,op;
}q[maxn<<1];
inline bool cmp1(query a,query b){return a.x<b.x;}
inline bool cmp2(query a,query b){return a.x>b.x;}
struct SegmentTree{
#define ls (ro<<1)
#define rs (ro<<1|1)
struct tree{
int l,r,mx;
}e[16000005];
void build(int ro,int l,int r){
e[ro].l=l,e[ro].r=r;
if(l==r){
e[ro].mx=-INF;return;
}
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
e[ro].mx=max(e[ls].mx,e[rs].mx);
}
void update(int ro,int k,int v){
int l=e[ro].l,r=e[ro].r;
if(l==r){
e[ro].mx=max(e[ro].mx,v);return;
}
int mid=l+r>>1;
if(k<=mid)update(ls,k,v);
else update(rs,k,v);
e[ro].mx=max(e[ls].mx,e[rs].mx);
}
void update2(int ro,int k,int v){
int l=e[ro].l,r=e[ro].r;
if(l==r){
e[ro].mx=v;return;
}
int mid=l+r>>1;
if(k<=mid)update2(ls,k,v);
else update2(rs,k,v);
e[ro].mx=max(e[ls].mx,e[rs].mx);
}
int query(int ro,int x,int y){
int l=e[ro].l,r=e[ro].r;
if(l==x and r==y)return e[ro].mx;
int mid=l+r>>1;
if(y<=mid)return query(ls,x,y);
else if(x>mid)return query(rs,x,y);
else return max(query(ls,x,mid),query(rs,mid+1,y));
}
#undef ls
#undef rs
}T;
void cdq(int l,int r){
if(l==r)return;
int mid=l+r>>1;
cdq(l,mid),cdq(mid+1,r);
int i,j;
sort(q+l,q+mid+1,cmp1),sort(q+mid+1,q+r+1,cmp1);
for(i=mid+1,j=l;i<=r;i++){
while(q[j].x<=q[i].x and j<=mid){
if(!q[j].op) T.update(1,q[j].y,q[j].x+q[j].y);j++;
}
if(q[i].op)ans[q[i].id]=min(ans[q[i].id],q[i].x+q[i].y-T.query(1,LN,q[i].y));
}
for(i=l;i<j;i++) T.update2(1,q[i].y,-INF); for(i=mid+1,j=l;i<=r;i++){
while(q[j].x<=q[i].x and j<=mid){
if(!q[j].op) T.update(1,q[j].y,q[j].x-q[j].y);j++;
}
if(q[i].op)ans[q[i].id]=min(ans[q[i].id],q[i].x-q[i].y-T.query(1,q[i].y,RN));
}
for(i=l;i<j;i++) T.update2(1,q[i].y,-INF); sort(q+l,q+mid+1,cmp2),sort(q+mid+1,q+r+1,cmp2);
for(i=mid+1,j=l;i<=r;i++){
while(q[j].x>=q[i].x and j<=mid){
if(!q[j].op) T.update(1,q[j].y,q[j].y-q[j].x);j++;
}
if(q[i].op)ans[q[i].id]=min(ans[q[i].id],q[i].y-q[i].x-T.query(1,LN,q[i].y));
}
for(i=l;i<j;i++) T.update2(1,q[i].y,-INF); for(i=mid+1,j=l;i<=r;i++){
while(q[j].x>=q[i].x and j<=mid){
if(!q[j].op) T.update(1,q[j].y,-q[j].y-q[j].x);j++;
}
if(q[i].op)ans[q[i].id]=min(ans[q[i].id],-q[i].y-q[i].x-T.query(1,q[i].y,RN));
}
for(i=l;i<j;i++) T.update2(1,q[i].y,-INF);
}
inline void work(){
n=read(),m=read();
memset(ans,INF,sizeof ans);
for(int i=1;i<=n;i++) q[i].x=read(),q[i].y=read(),LN=min(LN,q[i].y),RN=max(RN,q[i].y),q[i].op=0;
for(int i=n+1;i<=n+m;i++){
q[i].op=(read()-1);
q[i].id=q[i-1].id+q[i].op;
q[i].x=read(),q[i].y=read();
}
int mx=q[n+m].id;
T.build(1,LN,RN);
cdq(1,n+m);
for(int i=1;i<=mx;i++)print(ans[i]),*O++='\n';
fwrite(obuf,O-obuf,1,stdout);
}
}
signed main(){
star::work();
return 0;
}原以为是线段树常数过大,学习了树状数组解法然后WA了;
改回了线段树,把sort换成了merge,又WA;
只有最后一个办法了:改成树状数组+merge,要是再挂我就当场去学KD-tree.
写挂了,我滚去学K-D tree了。
K-D tree
K-D tree是一种维护多维空间点的数据结构,在这道题上是两维所以也叫作2-D tree。具体实现方式是将每个节点所在的区域切割递归建成二叉树,每一个树上的节点代表一个实际的结点,但又存储着选择这个结点时的区域大小。
建树
KDT在建树时,为了让它保持平衡,我们需要尽量选择所在空间维度的中位数,所以在保证时间复杂度正确的情况下我们可以使用nth_element函数,其作用是在线性时间内将一个数摆到它排序后应该在的位置,而将比它小的放在左边,比他大的放在右边(不重载运算符情况下是从小到大),但不是有序排列。
int build(int l,int r){
if(l>r)return 0;
int mid=l+r>>1;
double av[2],va[2];
av[0]=av[1]=va[0]=va[1]=0;
for(int i=l;i<=r;i++)av[0]+=e[g[i]].x,av[1]+=e[g[i]].y;
av[0]/=(r-l+1),av[1]/=(r-l+1);
for(int i=l;i<=r;i++)
va[0]+=(av[0]-e[g[i]].x)*(av[0]-e[g[i]].x),
va[1]+=(av[1]-e[g[i]].y)*(av[1]-e[g[i]].y);//寻找应该切割的维度
if(va[0]>va[1])nth_element(g+l,g+mid,g+r+1,cmpx),e[g[mid]].d=1;//d是维度
else nth_element(g+l,g+mid,g+r+1,cmpy),e[g[mid]].d=2;
e[g[mid]].ls=build(l,mid-1);
e[g[mid]].rs=build(mid+1,r);
maintain(g[mid]);//pushup函数,更新节点信息。
return g[mid];
}
插入
从root递归下去查找,按照当前节点所在维度向下插入,到达空节点时新建节点存储信息。
注意:当插入节点过多时KDT有可能失衡,此时我们需要将它拍扁重建(pia~)(因为KDT的结构,好像没有别的方法了?)\(^ ①\)
void insert(int &x,int k){
if(!x){
x=k,maintain(k);return;
}
if(e[x].d==1){
if(e[k].x<=e[x].x)insert(e[x].ls,k);
else insert(e[x].rs,k);
}else{
if(e[k].y<=e[x].y)insert(e[x].ls,k);
else insert(e[x].rs,k);
}
maintain(x);
if(bad(x)) rebuild(x);//pia
}
inline bool bad(int x){return 0.9*e[x].siz<=(double)max(e[e[x].ls].siz,e[e[x].rs].siz);}
//0.9为拍扁的阈值,越大拍扁越不频繁,但有可能失衡,按照实际情况调整。注意,实测其为0.8时会爆栈。
void getson(int x){
if(!x)return;
getson(e[x].ls);
g[++t]=x;
getson(e[x].rs);
}
inline void rebuild(int &x){
t=0;
getson(x);//找到所有被拍扁的节点(其实没必要,新建节点也行)
x=build(1,t);
}
查询
此题要求查询距离关键点最近的点的距离。
注意:我们在查询下传的时候需要比较的是区块位置距离关键点的距离,而统计答案是按照当前节点的坐标统计。
int ans=INF;//请不要学鄙人用全局变量传参,我被人嘴了
void query(int x){
cmin(ans,dist(x));
int dl=INF,dr=INF;
if(e[x].ls)dl=getdis(e[x].ls);
if(e[x].rs)dr=getdis(e[x].rs);
if(dl<dr){
if(dl<ans)query(e[x].ls);
if(dr<ans)query(e[x].rs);
}else{
if(dr<ans)query(e[x].rs);
if(dl<ans)query(e[x].ls);
}
}
完整代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<cmath>
#define cmin(i,j) (i)=min((i),(j))
#define cmax(i,j) (i)=max((i),(j))
using namespace std;
inline int read(){
int w=0,x=0;char c=getchar();
while(!isdigit(c))w|=c=='-',c=getchar();
while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
return w?-x:x;
}
namespace star
{
const int maxn=6e5+10,INF=0x3f3f3f3f;
int n,m,rt,tot,qx,qy,g[maxn],t;
struct node{
int x,y,ls,rs,l[2],r[2],siz,d;
}e[maxn];
inline bool cmpx(int a,int b){return e[a].x<e[b].x;}
inline bool cmpy(int a,int b){return e[a].y<e[b].y;}
inline void maintain(int x){
e[x].siz=e[e[x].ls].siz+e[e[x].rs].siz+1;
e[x].l[0]=e[x].r[0]=e[x].x;
e[x].l[1]=e[x].r[1]=e[x].y;
if(e[x].ls)
cmin(e[x].l[0],e[e[x].ls].l[0]),cmax(e[x].r[0],e[e[x].ls].r[0]),
cmin(e[x].l[1],e[e[x].ls].l[1]),cmax(e[x].r[1],e[e[x].ls].r[1]);
if(e[x].rs)
cmin(e[x].l[0],e[e[x].rs].l[0]),cmax(e[x].r[0],e[e[x].rs].r[0]),
cmin(e[x].l[1],e[e[x].rs].l[1]),cmax(e[x].r[1],e[e[x].rs].r[1]);
}
int build(int l,int r){
if(l>r)return 0;
int mid=l+r>>1;
double av[2],va[2];
av[0]=av[1]=va[0]=va[1]=0;
for(int i=l;i<=r;i++)av[0]+=e[g[i]].x,av[1]+=e[g[i]].y;
av[0]/=(r-l+1),av[1]/=(r-l+1);
for(int i=l;i<=r;i++)
va[0]+=(av[0]-e[g[i]].x)*(av[0]-e[g[i]].x),
va[1]+=(av[1]-e[g[i]].y)*(av[1]-e[g[i]].y);
if(va[0]>va[1])nth_element(g+l,g+mid,g+r+1,cmpx),e[g[mid]].d=1;
else nth_element(g+l,g+mid,g+r+1,cmpy),e[g[mid]].d=2;
e[g[mid]].ls=build(l,mid-1);
e[g[mid]].rs=build(mid+1,r);
maintain(g[mid]);
return g[mid];
}
void getson(int x){
if(!x)return;
getson(e[x].ls);
g[++t]=x;
getson(e[x].rs);
}
inline void rebuild(int &x){
t=0;
getson(x);
x=build(1,t);
}
inline bool bad(int x){return 0.9*e[x].siz<=(double)max(e[e[x].ls].siz,e[e[x].rs].siz);}
void insert(int &x,int k){
if(!x){
x=k,maintain(k);return;
}
if(e[x].d==1){
if(e[k].x<=e[x].x)insert(e[x].ls,k);
else insert(e[x].rs,k);
}else{
if(e[k].y<=e[x].y)insert(e[x].ls,k);
else insert(e[x].rs,k);
}
maintain(x);
if(bad(x)) rebuild(x);
}
inline int getdis(int x){return max(0,qx-e[x].r[0])+max(0,e[x].l[0]-qx)+max(0,qy-e[x].r[1])+max(0,e[x].l[1]-qy);}
inline int dist(int x){return abs(qx-e[x].x)+abs(qy-e[x].y);}
int ans;
void query(int x){
cmin(ans,dist(x));
int dl=INF,dr=INF;
if(e[x].ls)dl=getdis(e[x].ls);
if(e[x].rs)dr=getdis(e[x].rs);
if(dl<dr){
if(dl<ans)query(e[x].ls);
if(dr<ans)query(e[x].rs);
}else{
if(dr<ans)query(e[x].rs);
if(dl<ans)query(e[x].ls);
}
}
inline void work(){
n=read(),m=read();
while(n--){
e[++tot].x=read(),e[tot].y=read();
g[tot]=tot;
}
rt=build(1,tot);
while(m--){
if(read()==1){
e[++tot].x=read(),e[tot].y=read();
insert(rt,tot);
}else{
qx=read(),qy=read();ans=INF;query(rt);
printf("%d\n",ans);
}
}
}
}
signed main(){
star::work();
return 0;
}
注释
①K-D tree的重建方式含争议。事实上,作者是从OIwiki上学习的K-D tree写法,而在当页下方评论区中有人提出KDT不应该像替罪羊一样按照阈值重构,因为这样的话会在极端情况下多次重构导致时间复杂度退化。他提出,应该每插入\(\sqrt n\)个点后将全树进行重构,这样保证时间复杂度最劣情况下全部插入为\(O(n\sqrt n)\),查询最劣情况下单次为\(O(\sqrt n)\)。但是大家不用担心,愚以为大多数情况下本人这种写法的均摊时间复杂度是比另一种优的,实在不行可以适当调整阈值。欢迎大家激烈对线。
P4169-CDQ分治/K-D tree(三维偏序)-天使玩偶的更多相关文章
- CDQ分治学习笔记(三维偏序题解)
首先肯定是要膜拜CDQ大佬的. 题目背景 这是一道模板题 可以使用bitset,CDQ分治,K-DTree等方式解决. 题目描述 有 nn 个元素,第 ii 个元素有 a_iai.b_ibi.c_ ...
- CDQ分治嵌套模板:多维偏序问题
CDQ分治2 CDQ套CDQ:四维偏序问题 题目来源:COGS 2479 偏序 #define LEFT 0 #define RIGHT 1 struct Node{int a,b,c,d,bg;}; ...
- CDQ分治求不知道多少维偏序 (持续更新 ]
求三维偏序的模板 : //Author : 15owzLy1 //luogu3810.cpp //2018 12 25 16:31:58 #include <cstdio> #includ ...
- bzoj3262: 陌上花开(CDQ+树状数组处理三维偏序问题)
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3262 题目大意:中文题目 具体思路:CDQ可以处理的问题,一共有三维空间,对于第一维我们 ...
- P3810 【模板】三维偏序(陌上花开)(CDQ分治)
题目背景 这是一道模板题 可以使用bitset,CDQ分治,K-DTree等方式解决. 题目描述 有 nn 个元素,第 ii 个元素有 a_iai.b_ibi.c_ici 三个属性,设 f(i) ...
- 「分治」-cdq分治
cdq分治是一种分治算法: 一种分治思想,必须离线,可以用来处理序列上的问题(比如偏序问题),还可以优化1D/1D类型的DP.• 算法的大体思路我们可以用点对来描述.假定我们有一个长度为n的序列,要处 ...
- 2016计蒜之道复赛 百度地图的实时路况 floyd+cdq分治
链接:https://nanti.jisuanke.com/t/11217 奉上官方题解: 枚举 d(x , y , z) 中的 y,把 y 从这个图中删去,再求这时的全源最短路即可,使用 Floyd ...
- [学习笔记]CDQ分治和整体二分
序言 \(CDQ\) 分治和整体二分都是基于分治的思想,把复杂的问题拆分成许多可以简单求的解子问题.但是这两种算法必须离线处理,不能解决一些强制在线的题目.不过如果题目允许离线的话,这两种算法能把在线 ...
- P3810 【模板】三维偏序(陌上花开)
P3810 [模板]三维偏序(陌上花开) cdq分治+树状数组 三维偏序模板题 前两维用cdq分治,第三维用树状数组进行维护 就像用树状数组搞逆序对那样做--->存权值的出现次数 attenti ...
- 陌上花开——CDQ分治
传送门 “CDQ分治”从来都没有听说过,写了这题才知道还有这么神奇的算法. (被逼无奈).w(゚Д゚)w 于是看了不少dalao的博客,对CDQ算法粗浅地了解了一点.(想要了解CDQ的概念,可以看下这 ...
随机推荐
- 编译原理-翻译程序(Translator)
分为编译程序(compiler)和解释程序(interpreter) 编译程序:把源程序(高级语言编写)转换成目标程序(汇编语言或机器语言编写). 解释程序:对源程序边翻译边执行. 编译型语言 优点: ...
- 618技术特辑(三)直播带货王,“OMG买它”的背后,为什么是一连串技术挑战?
[本期推荐]为什么一到大促,我们的钱包总是被掏空?是大家自制力不够,还是电商平台太会读懂人心,从技术维度,抽丝剥茧一探究竟. 摘要:动辄几十上百万人同时在线的直播间,让所有人能同时公平的去抢购,并且还 ...
- anaconda安装VSCODE后,python报错
重新用anaconda时遇到了一点问题. 测试anaconda捆绑安装的VSCODE时写了一行print(1),然后报错. 后来发现用anaconda下载vscdoe时并不会给python一个路径,这 ...
- NOIP模拟测试29「爬山·学数数·七十和十七」
爬山题解不想写了 学数数 离散化然后找到以每一个值为最大值的连续子段有多少个,然后开个桶维护 那么怎么找以每一个值为最大值的连续子段个数 方法1(我的极笨的方法) 考试时我的丑陋思路, 定义极左值为左 ...
- Mybatis学习01:利用mybatis查询数据库
通过mybatis来操作mysql数据库的步骤大致可分为以下几步: 在这里,我们以对下面这个这个表格进行操作为例: 表名:ssm 1 配置依赖 在pom.xml中添加所需要的的依赖 <!-- m ...
- Golang封装一个加锁的Map工具包
Golang封装一个加锁的Map工具包 直接上代码了,用的是读写锁,代码如下: package utils import ( "sync" ) type BeeMap struct ...
- Golang控制子gorutine退出,并阻塞等待所有子gorutine全部退出
Golang控制子gorutine退出,并阻塞等待所有子gorutine全部退出 需求 程序有时需要自动重启或者重新初始化一些功能,就需要退出之前的所有子gorutine,并且要等待所有子goruti ...
- LevelDB学习笔记 (1):初识LevelDB
LevelDB学习笔记 (1):初识LevelDB 1. 写在前面 1.1 什么是levelDB LevelDB就是一个由Google开源的高效的单机Key/Value存储系统,该存储系统提供了Key ...
- 使用A Star 算法实现自动寻路详解
@ 目录 1.什么是A Start算法 2.A Star算法的原理和流程 2.1 前提 2.1.1 从起点开始扩散的节点 2.1.2 最短距离计算公式:F = G + H 2.1.3 欧几里得距离计算 ...
- 通过winsw将jar包做成window后台服务运行
第一步:下载Winsw地址 https://github.com/kohsuke/winsw/releases 第二步: 将下载好的sample-minimal.xml和WinSW.NET4.exe ...