2019牛客暑假多校训练赛第八场 D Distance(定期重构bfs/三维空间,平面bfs/三维树状数组)

题目链接:https://ac.nowcoder.com/acm/contest/888/D

题意:给定一个三维空间n,m,h,有两个操作,1是在这个三维空间的某个位置插入一个点,2是访问距离这个点的其余点的最小曼哈顿距离。

数据范围:1≤n×m×h,q≤1e5,1≤x≤n,1≤y≤m,1≤z≤h

 

思路1:首先因为n×m×h<=1e5,首先为了表示这个空间我们肯定要开一个数组,所以我们可以旋转这个空间让中间值(或最小值)做高,这样我们只需要开320*1e5的二维数组,1e5代表长和宽的乘积,这样我们就把这个三维空间拆成了320个平面。那么对于操作1,插入一个点我们只需要在这个点对应的平面那个高度的平面进行bfs,这个bfs是为了以后的询问做贡献的,这个bfs是以插入的点为起点向外搜的,那么对于某个平面它的数组里就存好了在这个平面上的那些点到这个点的最小曼哈顿距离(具体看代码的bfs就懂了).对于操作2,只需要对于这320个平面扫一遍访问的这个点出现的长和宽的坐标那么答案就是min(d[i][长和宽坐标]+\Deltah)

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int inf=1e9+7;
const int dx[4]={0,0,-1,1};
const int dy[4]={-1,1,0,0};
int n,m,h,q,o;
void trans(int &x, int &y, int &z){
    if(o==1)swap(x,z);
    if(o==2)swap(y,z);
}
int d[320][N];///320是sqrt(1e5);
void bfs(int x,int y,int z){
    queue<int>Q;
    int s=x*m+y;
    d[z][s]=0;
    Q.push(s);
    while(!Q.empty()){
        int cur=Q.front();
        Q.pop();
        int x0=cur/m,y0=cur%m;
        for(int j=0;j<4;j++){
            int xx=x0+dx[j],yy=y0+dy[j];
            if(xx>=0&&xx<n&&yy>=0&&yy<m){
                int to=xx*m+yy;
                if(d[z][cur]+1<d[z][to]){
                    d[z][to]=d[z][cur]+1;
                    Q.push(to);
                }
            }
        }
    }
}
int main(){
    scanf("%d%d%d%d",&n,&m,&h,&q);
    int a[]={n,m,h};
    sort(a,a+3);
    if(n==a[0])o=1;
    else if(m==a[0])o=2;
    trans(n,m,h);
    for(int i=1;i<=h;i++)for(int j=0;j<n;j++)for(int k=0;k<m;k++)
    d[i][j*m+k]=inf;
    for(int i=1,op,x,y,z;i<=q;i++){
        scanf("%d%d%d%d",&op,&x,&y,&z);
        trans(x,y,z);x--,y--;
        if(op==1)bfs(x,y,z);
        else{
            int ans=inf;
            for(int i=1;i<=h;i++)ans=min(ans,d[i][x*m+y]+abs(z-i));
            printf("%d\n",ans);
        }
    }
    return 0;
}

思路2:定期重构。继续采用上面的方法对每个点对地图bfs,但是我们可以分组来,也就是定期重构,当我们手中要插入的点的个数小于一个阈值时,我们就只直接暴力对每个点求一下曼哈顿距离就可以了,当大于阈值时一次性将手中的点全部拿出来bfs,然后手中的点的个数清零就好了。

复杂度证明:假设阈值是E那么插入的复杂度是qnmh/E,询问的复杂度是qE,所以总的复杂度是qnmh/E+qE,根据均值不等式可知当E=sqrt(nmh)时理论复杂度最低,也就320。

#include <bits/stdc++.h>
#define pb(x) push_back(x)
using namespace std;
const int N=1e5+5;
const int MX=320;///和数据有关
vector<int>X,Y,Z;
int dis[N],n,m,h,q;
int dir[6][3]={1,0,0,-1,0,0,0,1,0,0,-1,0,0,0,1,0,0,-1};
int has(int x,int y,int z){return x*m*h+y*h+z;}
struct p{int x,y,z;};
void rebuild(){
    queue<p>Q;
    for(int i=0;i<MX;i++){
        dis[has(X[i],Y[i],Z[i])]=0;
        Q.push({X[i],Y[i],Z[i]});
    }
    while(!Q.empty()){
        p A=Q.front();Q.pop();
        for(int i=0;i<6;i++){
            int px=A.x+dir[i][0],py=A.y+dir[i][1],pz=A.z+dir[i][2];
            if(px<0||py<0||pz<0||px>=n||py>=m||pz>=h||dis[has(px,py,pz)]<=dis[has(A.x,A.y,A.z)]+1) continue;
            dis[has(px,py,pz)]=dis[has(A.x,A.y,A.z)]+1;
            Q.push({px,py,pz});
        }
    }
    X.clear(),Y.clear(),Z.clear();
}
int main(){
    scanf("%d%d%d%d",&n,&m,&h,&q);
    int op,x,y,z,ans;
    memset(dis,0x7f7f7f7f,sizeof(dis));
    while(q--){
        scanf("%d%d%d%d",&op,&x,&y,&z);
        --x,--y,--z;
        if(op==1)X.pb(x),Y.pb(y),Z.pb(z);
        else{
            ans=dis[has(x,y,z)];
            int len=X.size();
            for(int i=0;i<len;i++)ans=min(ans,abs(x-X[i])+abs(y-Y[i])+abs(z-Z[i]));
            printf("%d\n",ans);
        }
        if(X.size()==MX)rebuild();
    }
    return 0;
}

思路3:三维树状数组维护曼哈顿距离拆掉绝对值以后的情况,情况一共有八种

1.\left | x2-x1 \right |+\left | y2-y1 \right |+\left | z2-z1 \right |=x2-x1+y2-y1+z2-z1

2.\left | x2-x1 \right |+\left | y2-y1 \right |+\left | z2-z1 \right |=x2-x1+y2-y1+z1-z2

3.\left | x2-x1 \right |+\left | y2-y1 \right |+\left | z2-z1 \right |=x2-x1+y1-y2+z1-z2

4.\left | x2-x1 \right |+\left | y2-y1 \right |+\left | z2-z1 \right |=x2-x1+y1-y2+z2-z1

5.\left | x2-x1 \right |+\left | y2-y1 \right |+\left | z2-z1 \right |=x1-x2+y2-y1+z2-z1

6.\left | x2-x1 \right |+\left | y2-y1 \right |+\left | z2-z1 \right |=x1-x2+y1-y2+z2-z1

7.\left | x2-x1 \right |+\left | y2-y1 \right |+\left | z2-z1 \right |=x1-x2+y2-y1+z1-z2

8.\left | x2-x1 \right |+\left | y2-y1 \right |+\left | z2-z1 \right |=x1-x2+y1-y2+z1-z2

时间复杂度O(8*q*logn*logm*logh)最坏的情况就是n=m=h=47时复杂度最高为O(8*6^3*q)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=1<<30;
const int N=3e5+5;
int n,m,h,q;
void gmax(int &a,int b){if(a<b)a=b;}
void gmin(int &a,int b){if(a>b)a=b;}
struct BIT{
    int a[N];
    inline int lowbit(int x){return x&-x;}
    inline int has(int x,int y,int z){return x*m*h+y*h+z;}
    void init(){for(int i=0;i<N;i++)a[i]=-inf;}
    void update(int x,int y,int z,int val){
        for(int i=x; i<=n; i+=lowbit(i))
        for(int j=y; j<=m; j+=lowbit(j))
        for(int k=z; k<=h; k+=lowbit(k))
            gmax(a[has(i,j,k)],val);
    }
    int query(int x,int y,int z){
        int ans=-inf;
        for(int i=x; i; i-=lowbit(i))
        for(int j=y; j; j-=lowbit(j))
        for(int k=z; k; k-=lowbit(k))
            ans=max(ans,a[has(i,j,k)]);
        return ans;
    }
}tree[8];
int main(){
    for(int i=0; i<8; i++)tree[i].init();
    scanf("%d%d%d%d",&n,&m,&h,&q);
    int op,x,y,z,cnt=0;
    while(q--){
        scanf("%d%d%d%d",&op,&x,&y,&z);
        if(op==1){
            tree[0].update(x,y,z,x+y+z);
            tree[1].update(x,y,h-z+1,x+y-z);
            tree[2].update(x,m-y+1,z,x-y+z);
            tree[3].update(x,m-y+1,h-z+1,x-y-z);
            tree[4].update(n-x+1,y,z,-x+y+z);
            tree[5].update(n-x+1,y,h-z+1,-x+y-z);
            tree[6].update(n-x+1,m-y+1,z,-x-y+z);
            tree[7].update(n-x+1,m-y+1,h-z+1,-x-y-z);
        }
         else if(op==2){
            int ans=inf;
            gmin(ans,x+y+z-tree[0].query(x,y,z));
            gmin(ans,x+y-z-tree[1].query(x,y,h-z+1));
            gmin(ans,x-y+z-tree[2].query(x,m-y+1,z));
            gmin(ans,x-y-z-tree[3].query(x,m-y+1,h-z+1));
            gmin(ans,-x+y+z-tree[4].query(n-x+1,y,z));
            gmin(ans,-x+y-z-tree[5].query(n-x+1,y,h-z+1));
            gmin(ans,-x-y+z-tree[6].query(n-x+1,m-y+1,z));
            gmin(ans,-x-y-z-tree[7].query(n-x+1,m-y+1,h-z+1));
            printf("%d\n",ans);
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值