n*m*h<=1e5的三维空间内有q<=1e5次操作,分两种:
1. 给点(x,y,z)打标记
2.询问离位置A(x,y,z)最近的标记点到A的最近曼哈顿距离。
两种做法:
1.分块/定期重构
如果所有的询问操作都在打标记后,那么从所有的标记点开始做一次多源bfs求最短路(每次可以走上下左右前后6个方向哦),则询问点的dis就是到离它最近的点的曼哈顿距离。
分块的话,就把所有q个操作分成q^1/2块,假设当前要处理的块是i,那么i之前的块中标记点全都做一遍bfs,那么对于i内的询问点,只需要比较(这个询问点的dis) 和 ( 块内该询问点之前的所有标记点之间的距离),每一块做一次bfs是nmhq^1/2,每次块内暴力一下复杂度是q,所以复杂度是q^1/2*(nmh+q^1/2)。大概是这样吧……
定期重构就是把标记点攒一攒,攒到一定程度了就bfs更新掉。那么更新掉了的点会通过dis体现,还攒着没更新的就直接和当前查询点比一下,这些距离和dis比一下取个最小的就是啦。
2.三维BIT
可以参考之前写的最近曼哈顿距离的博客,https://blog.youkuaiyun.com/Mr_Alice/article/details/99765632。
二维变三维而已啦~
然后用三维树状数组维护了一下区间最小值~(其实就是前缀最小值咳咳)
感觉这个做法超酷O口O
以下是代码:
分块——103ms
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int n,m,h,Q,top;
struct node{
int x,y,z;
}st[maxn];
queue<node>q;
int dis[maxn];
int id(int x,int y,int z){ return (x-1)*m*h+(y-1)*h+z;}
int dir[6][3]={1,0,0,-1,0,0,0,1,0,0,-1,0,0,0,1,0,0,-1};
void upd(){
while(!q.empty()) q.pop();
for(int i=0;i<top;i++){
dis[id(st[i].x,st[i].y,st[i].z)]=0;
q.push(st[i]);
}
while(!q.empty()){
node now=q.front();q.pop();
for(int i=0;i<6;i++){
int nx=now.x+dir[i][0],ny=now.y+dir[i][1],nz=now.z+dir[i][2];
if(nx<1||ny<1||nz<1||nx>n||ny>m||nz>h) continue;
if(dis[id(nx,ny,nz)]>dis[id(now.x,now.y,now.z)]+1){
dis[id(nx,ny,nz)]=dis[id(now.x,now.y,now.z)]+1;
q.push({nx,ny,nz});
}
}
}
top=0;
}
int main(){
scanf("%d%d%d%d",&n,&m,&h,&Q);
int lim=sqrt(n*m*h);
memset(dis,127,sizeof(dis));
while(Q--){
int op,x,y,z;
scanf("%d%d%d%d",&op,&x,&y,&z);
if(op==1){
st[top++]={x,y,z};
}else{
int res=dis[id(x,y,z)];
for(int i=0;i<top;i++){
res=min(res,abs(x-st[i].x)+abs(y-st[i].y)+abs(z-st[i].z));
}
printf("%d\n",res);
}
if(top>=lim){
upd();
}
}
return 0;
}
三维BIT——164ms
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+15;
int n,m,h,q;
struct BIT{
int d[maxn];
void init(){memset(d,128,sizeof(int)*(n*m*h+5));}
inline int getid(int i,int j,int k){return (i-1)*m*h+(j-1)*h+k;}
inline void getmax(int &a,int b){ a=(b>a)?b:a;}
inline void update(int x,int y,int z,int val){
// printf(" x=%d y=%d z=%d val=%d\n",x,y,z,val);
for(int i=x;i<=n;i+=i&-i)
for(int j=y;j<=m;j+=j&-j)
for(int k=z;k<=h;k+=k&-k)
getmax(d[getid(i,j,k)],val);//getid(i,j,k)这个位置被更新了一个值
}
inline int sum(int x,int y,int z){
int res=-2e9;
for(int i=x;i>0;i-=i&-i)//询问所有x'<=x,y'<=y,z'<=z范围内的最大值,此处x,x'等都是包括符号了的哦
for(int j=y;j>0;j-=j&-j)
for(int k=z;k>0;k-=k&-k)
getmax(res,d[getid(i,j,k)]);
return res;
}
}t[8];
int main(){
scanf("%d%d%d%d",&n,&m,&h,&q);
for(int i=0;i<8;i++) t[i].init();
int op,x,y,z,xx,yy,zz,xxx,yyy,zzz,ans;
while(q--){
ans=2e9;
scanf("%d%d%d%d",&op,&x,&y,&z);
for(int i=0;i<8;i++){
if(4&i) xx=xxx=x;//+1是因为树状数组下标不可以为负
else xx=n-x+1,xxx=-x;
if(2&i) yy=yyy=y;
else yy=m-y+1,yyy=-y;
if(1&i) zz=zzz=z;
else zz=h-z+1,zzz=-z;
if(op==1){
t[i].update(xx,yy,zz,xxx+yyy+zzz);
}else{
ans=min(ans,xxx+yyy+zzz-t[i].sum(xx,yy,zz));
}
}
if(op==2) printf("%d\n",ans);
}
return 0;
}