夕拾算法进阶篇:30)三维BFS广度搜索(图论)

来源:互联网 发布:龙纹鏊淘宝 编辑:程序博客网 时间:2024/05/16 00:55

之前介绍过二维广度搜索以及如何求解当前坐标的相邻坐标,现在把问题延伸到三维,并且也只能向相邻的位置(前,后,上,下,左,右)移动如何求当前坐标(x,y,z)的下一个坐标?其实很简单三维无法就多了一个前后。可以先固定前后的方向(假设为z轴),此时只有在上下左右(xy轴)方向移动,这和二维无异;然后再固定xy轴,只在z轴上移动。

//定义方向依次是前,后,上,下,左,右 int dir[6][3]={{1,0,0}, {-1,0,0}, {0,-1,0}, {0,1,0}, {0,0,-1}, {0,0,1}}; for(i=0;i<6;i++){int nx=cur.x+dir[i][0];int ny=cur.y+dir[i][1];int nz=cur.z+dir[i][2];}
下面看几个例题感受下三维广度搜索

题目:胜利大逃亡

Problem Description
Ignatius被魔王抓走了,有一天魔王出差去了,这可是Ignatius逃亡的好机会.魔王住在一个城堡里,城堡是一个A*B*C的立方体,可以被表示成A个B*C的矩阵,刚开始Ignatius被关在(0,0,0)的位置,离开城堡的门在(A-1,B-1,C-1)的位置,现在知道魔王将在T分钟后回到城堡,Ignatius每分钟能从一个坐标走到相邻的六个坐标中的其中一个.现在给你城堡的地图,请你计算出Ignatius能否在魔王回来前离开城堡(只要走到出口就算离开城堡,如果走到出口的时候魔王刚好回来也算逃亡成功),如果可以请输出需要多少分钟才能离开,如果不能则输出-1.


Input
输入数据的第一行是一个正整数K,表明测试数据的数量.每组测试数据的第一行是四个正整数A,B,C和T(1<=A,B,C<=50,1<=T<=1000),它们分别代表城堡的大小和魔王回来的时间.然后是A块输入数据(先是第0块,然后是第1块,第2块......),每块输入数据有B行,每行有C个正整数,代表迷宫的布局,其中0代表路,1代表墙.(如果对输入描述不清楚,可以参考Sample Input中的迷宫描述,它表示的就是上图中的迷宫)
特别注意:本题的测试数据非常大,请使用scanf输入,我不能保证使用cin能不超时.在本OJ上请使用Visual C++提交.
Output
对于每组测试数据,如果Ignatius能够在魔王回来前离开城堡,那么请输出他最少需要多少分钟,否则输出-1.
Sample Input
1
3 3 4 20
0 1 1 1
0 0 1 1
0 1 1 1
1 1 1 1
1 0 0 1
0 1 1 1
0 0 0 0
0 1 1 0
0 1 1 0
Sample Output
11
 

分析:有了之前二维的BFS模板和上面三维坐标移动的知识,就可以很好地解决这个问题。

#include <iostream>#include <cstring>#include <cstdio>#include <queue>using namespace std;const int M=52;bool map[M][M][M]; //存储地图 int a,b,c,t;  //定义方向依次是前,后,上,下,左,右 int dir[6][3]={{1,0,0}, {-1,0,0}, {0,-1,0}, {0,1,0}, {0,0,-1}, {0,0,1}}; struct Node{    int x,y,z,t;//下一个点的坐标以及当前用的时间     Node(){}    Node(int x,int y,int z,int t):x(x),y(y),z(z),t(t){}};int bfs(int sx,int sy,int sz){    int nx,ny,nz,i;    queue<Node> q;    q.push(Node(sx,sy,sz,0));    map[sx][sy][sz]=1; //该点入队了,直接在地图上设置为1(墙) 省掉了vis标记数组     while(!q.empty()){        Node cur=q.front(); q.pop();        if(cur.x==a-1&&cur.y==b-1&&cur.z==c-1){            return cur.t; //达到出口         }        for(i=0;i<6;i++){            nx=cur.x+dir[i][0];            ny=cur.y+dir[i][1];            nz=cur.z+dir[i][2];            if(nx>=0&&nx<a&&ny>=0&&ny<b&&nz>=0&&nz<c){//下标不越界                 if(!map[nx][ny][nz] && cur.t<t){ //下一个点可达,且当前时间小于给定时间t                     q.push(Node(nx,ny,nz,cur.t+1));//下一个点入队                     map[nx][ny][nz]=1; //该点入队了,直接在地图上设置为1(墙)                }            }        }    }    return -1; //无法在t时间内到达出口 }int main(){    int n,i,j,k;    scanf("%d",&n);    while(n--){        scanf("%d%d%d%d",&a,&b,&c,&t);        for(i=0;i<a;i++){            for(j=0;j<b;j++){                for(k=0;k<c;k++){                    scanf("%d",&map[i][j][k]);                }            }        }        int ans=bfs(0,0,0);        printf("%d\n",ans);    }}

题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=1253


题目名称:游戏

问题描述
  小明在玩一个电脑游戏,游戏在一个n×m的方格图上进行,小明控制的角色开始的时候站在第一行第一列,目标是前往第n行第m列。
  方格图上有一些方格是始终安全的,有一些在一段时间是危险的,如果小明控制的角色到达一个方格的时候方格是危险的,则小明输掉了游戏,如果小明的角色到达了第n行第m列,则小明过关。第一行第一列和第n行第m列永远都是安全的。
  每个单位时间,小明的角色必须向上下左右四个方向相邻的方格中的一个移动一格。
  经过很多次尝试,小明掌握了方格图的安全和危险的规律:每一个方格出现危险的时间一定是连续的。并且,小明还掌握了每个方格在哪段时间是危险的。
  现在,小明想知道,自己最快经过几个时间单位可以达到第n行第m列过关。
输入格式
  输入的第一行包含三个整数n, m, t,用一个空格分隔,表示方格图的行数n、列数m,以及方格图中有危险的方格数量。
  接下来t行,每行4个整数r, c, a, b,表示第r行第c列的方格在第a个时刻到第b个时刻之间是危险的,包括a和b。游戏开始时的时刻为0。输入数据保证r和c不同时为1,而且当r为n时c不为m。一个方格只有一段时间是危险的(或者说不会出现两行拥有相同的r和c)。
输出格式
  输出一个整数,表示小明最快经过几个时间单位可以过关。输入数据保证小明一定可以过关。
样例输入
3 3 3
2 1 1 1
1 3 2 10
2 2 2 10
样例输出
6
样例说明
  第2行第1列时刻1是危险的,因此第一步必须走到第1行第2列。
  第二步可以走到第1行第1列,第三步走到第2行第1列,后面经过第3行第1列、第3行第2列到达第3行第3列。
评测用例规模与约定
  前30%的评测用例满足:0 < n, m ≤ 10,0 ≤ t < 99。
  所有评测用例满足:0 < n, m ≤ 100,0 ≤ t < 9999,1 ≤ r ≤ n,1 ≤ c ≤ m,0 ≤ a ≤ b ≤ 100。


分析:很明显入队的元素还能继续入队,虽然可以到达目标点但是效率非常低,如何提高某些访问后的点,让其无法在入队? 空间不够,时间来凑,再添加一维为时间维度。现在考虑下时间维最大是多少?考虑极端的情况,就是其一直在启点来回的移动,最大耗时是max(b)=100,安全后可以直接通向耗时m+n=200,因此为了防止临界的数据,推荐第三维最大设置为300。事实上,添加时间维后,只能提高了部分的效率,因为入队的点出队后还可以入队,因此某些顶点的抖动还是存在的,目前还没有找到提高效率的方法,如果有朋友知道,可以告知下笔者。

#include <iostream>#include <cstring>#include <queue>using namespace std;const int M=102;bool inq[M][M][M*3]; //三维数组表示是否入队过,第三维为时间 int dir[4][2]={{ -1, 0}, {1, 0}, {0, -1}, {0, 1}}; //定义上下左右的方向 class Block{public:int a,b;Block(){ a=b=0;}void set(int _a,int _b){a=_a; b=_b;}}block[M][M]; //block[i][j]表示其在a-b时刻是阻塞的 class Node{public:int x,y,step;Node(){}Node(int _x,int _y,int _step){x=_x; y=_y; step=_step;}};int main(){int n,m,t,i,j,r,c,a,b,ny,nx,ns;cin>>n>>m>>t;for(i=0;i<t;i++){cin>>r>>c>>a>>b;block[r][c].set(a,b);}queue<Node> q;q.push(Node(1,1,0)); //初始化队列 inq[1][1][0]=true; //初始位置入队 while(!q.empty()){Node cur=q.front(); q.pop();cout<<cur.x<<" "<<cur.y<<" "<<cur.step<<endl;if(cur.x==n&&cur.y==m){ //到达出口 cout<<cur.step<<endl; break;}for(i=0;i<4;i++){ //遍历相邻的位置 nx=cur.x+dir[i][0];ny=cur.y+dir[i][1];ns=cur.step+1;if(nx<1||nx>n||ny<1||ny>m) continue; //下标越界 if(inq[nx][ny][ns]) continue; //已经入队过 if(block[nx][ny].a<=ns && ns<=block[nx][ny].b) continue; //当前位置在阻塞中.. q.push(Node(nx,ny,ns)); //合法可以入队 inq[nx][ny][ns]=true; //设置位置在该时间已经入队 }}} 
题目来源: CCFCSP201604-4

0 0