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的概念,可以看下这 ...
随机推荐
- Spring Cloud04: RestTemplate的使用
上一篇我们已经学会了如何创建一个服务提供者,那么这一篇我们来创建一个服务消费者,实现思路是先通过Spring boot搭建一个微服务应用,再通过Eureka Client把它注册到注册中心Eureka ...
- fiddler操作详情
1.设置fiddler请求过滤 2.请求与响应的格式内容 3.拦截请求操作 a.按F11开始拦截,发送请求 b.修改请求数据 c.SHIFT+F11关闭拦截 d.run to complete,把修 ...
- 【NX二次开发】Block UI 集列表
属性说明 属性 类型 描述 常规 BlockID String 控件ID Enable Logical 是否可操作 Group ...
- 【NX二次开发】批量数字签名的方法,解决自己电脑编译的dll在用户正版NX无法使用的问题
在UG5.0开始,所有开发的DLL都要"签名"后才能被客户端上正版的NX调用. 1. 如果是基于c++开发的dll,使用如下方法可以顺利签名成功(这里借用网上现有的文字和图片) 1 ...
- 【linux】驱动-12-并发与竞态
目录 前言 12. 并发&竞态 12.1 并发&竞态概念 12.2 竞态解决方法 12.3 原子 12.3.1 原子介绍 12.3.2 原子操作步骤 12.3.3 原子 API 12. ...
- .NET Core/.NET5/.NET6 开源项目汇总3:工作流组件
系列目录 [已更新最新开发文章,点击查看详细] 开源项目是众多组织与个人分享的组件或项目,作者付出的心血我们是无法体会的,所以首先大家要心存感激.尊重.请严格遵守每个项目的开源协议后再使用.尊 ...
- Ubuntu安装ibmmq
一.前言 安装整个ibmmq的过程中,真的气炸了,在网上搜索到的答案千篇一律,一个安装部署文档居然没有链接地址:为了找到这个开发版本的下载地址找了一下午,不容易啊.也提醒了自己写博文还是得有责任心,把 ...
- 磁盘文件监控(Java)并发送邮件通知、系统定期执行的办法
以下是通过xml文件进行的监控路径.文件以及邮件信息的配置,读取xml文件使用的是三方jar包:dom4j 收发邮件采用的是最普通的javamail,需要两个jar包,mail.jar和activit ...
- qemu-ga windows下的安装及监控开发
windows安装qemu-ga 虚拟机配置里添加virtio serial端口 #virsh edit instance-name devices里添加下面这段配置, 1 <channel t ...
- 数据权限筛选(RLS)的两种实现介绍
在应用程序中,尤其是在统计的时候, 需要使用数据权限来筛选数据行. 简单的说,张三看张三部门的数据, 李四看李四部门的数据:或者员工只能看自己的数据, 经理可以看部门的数据.这个在微软的文档中叫Row ...