P4169-CDQ分治/K-D tree(三维偏序)-天使玩偶

这是一篇两种做法都有的题解

题外话

我写吐了……

本着不看题解的原则,没写(不会)K-D tree,就写了个cdq分治的做法。下面是我的写题步骤:

  1. 想着树状数组维护不了区间最值,于是写了线段树,因为一个**的错误调了几个小时;

  2. cdq只写了两个方向。显然是错的,因为没考虑修改。所以挂了;

  3. 加上另外两个方向,正确性终于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;
    }
  4. 原以为是线段树常数过大,学习了树状数组解法然后WA了;

  5. 改回了线段树,把sort换成了merge,又WA;

  6. 只有最后一个办法了:改成树状数组+merge,要是再挂我就当场去学KD-tree.

  7. 写挂了,我滚去学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(三维偏序)-天使玩偶的更多相关文章

  1. CDQ分治学习笔记(三维偏序题解)

    首先肯定是要膜拜CDQ大佬的. 题目背景 这是一道模板题 可以使用bitset,CDQ分治,K-DTree等方式解决. 题目描述 有 nn 个元素,第 ii 个元素有 a_iai​.b_ibi​.c_ ...

  2. CDQ分治嵌套模板:多维偏序问题

    CDQ分治2 CDQ套CDQ:四维偏序问题 题目来源:COGS 2479 偏序 #define LEFT 0 #define RIGHT 1 struct Node{int a,b,c,d,bg;}; ...

  3. CDQ分治求不知道多少维偏序 (持续更新 ]

    求三维偏序的模板 : //Author : 15owzLy1 //luogu3810.cpp //2018 12 25 16:31:58 #include <cstdio> #includ ...

  4. bzoj3262: 陌上花开(CDQ+树状数组处理三维偏序问题)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3262 题目大意:中文题目 具体思路:CDQ可以处理的问题,一共有三维空间,对于第一维我们 ...

  5. P3810 【模板】三维偏序(陌上花开)(CDQ分治)

    题目背景 这是一道模板题 可以使用bitset,CDQ分治,K-DTree等方式解决. 题目描述 有 nn 个元素,第 ii 个元素有 a_iai​.b_ibi​.c_ici​ 三个属性,设 f(i) ...

  6. 「分治」-cdq分治

    cdq分治是一种分治算法: 一种分治思想,必须离线,可以用来处理序列上的问题(比如偏序问题),还可以优化1D/1D类型的DP.• 算法的大体思路我们可以用点对来描述.假定我们有一个长度为n的序列,要处 ...

  7. 2016计蒜之道复赛 百度地图的实时路况 floyd+cdq分治

    链接:https://nanti.jisuanke.com/t/11217 奉上官方题解: 枚举 d(x , y , z) 中的 y,把 y 从这个图中删去,再求这时的全源最短路即可,使用 Floyd ...

  8. [学习笔记]CDQ分治和整体二分

    序言 \(CDQ\) 分治和整体二分都是基于分治的思想,把复杂的问题拆分成许多可以简单求的解子问题.但是这两种算法必须离线处理,不能解决一些强制在线的题目.不过如果题目允许离线的话,这两种算法能把在线 ...

  9. P3810 【模板】三维偏序(陌上花开)

    P3810 [模板]三维偏序(陌上花开) cdq分治+树状数组 三维偏序模板题 前两维用cdq分治,第三维用树状数组进行维护 就像用树状数组搞逆序对那样做--->存权值的出现次数 attenti ...

  10. 陌上花开——CDQ分治

    传送门 “CDQ分治”从来都没有听说过,写了这题才知道还有这么神奇的算法. (被逼无奈).w(゚Д゚)w 于是看了不少dalao的博客,对CDQ算法粗浅地了解了一点.(想要了解CDQ的概念,可以看下这 ...

随机推荐

  1. 解决java socket在传输汉字时出现截断导致乱码的问题

    解决java socket在传输汉字时出现截断导致乱码的问题 当使用socket进行TCP数据传输时,传输的字符串会编码成字节数组,当采用utf8编码时,数字与字母长度为1个字节,而汉字一般为3个字节 ...

  2. asp.net core 实现 face recognition 使用 tensorflowjs(源代码)

    功能描述 上传照片文件名及是系统要识别标签或是照片的名称(人物标识) 提取照片脸部特征值(调用 facemesh模型) 保存特征值添加样本(调用 knnClassifier) 测试上传的图片是否识别正 ...

  3. DOS命令行(1)——Windows目录与文件应用操作

    cd 1.使用cd快速切换到指定盘符与目录中 命令格式1:cd [/d] [<盘符>][<路径>] 或 chdir [/d] [<盘符>][<路径>] ...

  4. redis学习第二天

    Redis 在 2.8.9 版本之后添加了 HyperLogLog 结构 Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常 ...

  5. 每日三道面试题,通往自由的道路6——JVM

    茫茫人海千千万万,感谢这一秒你看到这里.希望我的面试题系列能对你的有所帮助!共勉! 愿你在未来的日子,保持热爱,奔赴山海! 每日三道面试题,成就更好自我 今天我们继续聊聊JVM的话题吧! 1. 那你知 ...

  6. 数据同步Datax与Datax_web的部署以及使用说明

    一.DataX3.0概述 DataX 是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL.Oracle等).HDFS.Hive.ODPS.HBase.FTP等各种异构数据源之间稳定高 ...

  7. SSH远程登录相关教程

    命令概述 命令 英文 ssh 用户名@ip secure shell scp 用户名@ip:文件名或路径 用户名@ip:文件名或路径 secure copy 在 Linux 中 SSH 是 非常常用 ...

  8. 10、ssh中scp、sftp程序详解

    每次都是全量拷贝,rsync是增量拷贝 10.1.scp的基本用法: -r:拷贝目录; -p: 保持属性: -l:限速设置; scp -P52113 /etc/hosts lc@172.16.1.41 ...

  9. LeetCode解题记录(贪心算法)(一)

    1. 前言 目前得到一本不错的算法书籍,页数不多,挺符合我的需要,于是正好借这个机会来好好的系统的刷一下算法题,一来呢,是可以给部分同学提供解题思路,和一些自己的思考,二来呢,我也可以在需要复习的时候 ...

  10. Linux指令手册 (一)

    指令格式 指令主体 [选项] [操作对象] 一个完整的指令是由"指令主体"."选项"和"操作对象"组成的,其中指令主体只能有一个,选项有零个 ...