ACM-Problem : 救公主续
Description
公主又被魔王抓去了!公主被关在一个矩形迷宫里,有的位置有守卫把守。年迈的国王正是心急如焚,告招天下勇士来拯救公主。不过公主早已习以为常,她深信智勇的骑士肯定又会将她救出。初始时骑士在迷宫左上角(0,0)处,公主在右下角(n-1,m-1),骑士可向上下左右四个方向走,每走一步需要一个单位时间,如果该位置有守卫,需要消耗一定的时间来对付,问骑士最快需要多少时间到达公主。
Input
输入有多组,每组第一行两个不超过100的整数n,m,表示矩形的长和宽,接下的n行m列代表迷宫,'X'代表障碍物,'.'代表道路,数字则表示该位置有守卫,对付该守卫需要的时间为这个数值
Output
输出骑士到公主位置的最小时间,格式见样例,若不能到达输出“God please help our poor hero.”
Sample Input
5 6 .XX.1. ..X.2. 2...X. ...XX. XXXXX. 5 6 .XX... ..XX1. 2...X. ...XX. XXXXX.
Sample Output
It takes 13 seconds to reach the target position. God please help our poor hero.
HINT
过程如下
/*
** author: shydream
** time: 2016.7.31
** 这里面有几处问题,做了标记,后面会讨论
**/
#include<string.h>
#include<stdio.h>
#include<queue>
#include<iostream>
#define maxn 110
using namespace std;//刚上来少了这一句,结果后边一大串的命名错误 ;
//T 1:优先队列
struct node
{
int x,y,dir,time;
/*横纵坐标x、y,dir表示方向,不过本题没有用处,可以忽略,time表示当前行动已用时间 ; */
friend bool operator < (node a,node b)
/*进行重载,使用优先队列,小的值在队列顶部 ;不用优先队列的话后面会出现不可预知的错误 ; */
{
return a.time>b.time;
}
};
int n,m;
char maps[maxn][maxn]; //地图 ;
int vis[maxn][maxn]; //改点坐标是否被访问过 ;
int dirs[4][2]={1,0,-1,0,0,1,0,-1}; //移动方向为下、上、左、右
void bfs()
{
memset(vis,0,sizeof(vis));
//每一步循环前要注意某些数据是否进行了初始化。
int i;
priority_queue<node>q ;
node pre,nxt; //当前与接下来;
pre.x=0;
pre.y=0;
pre.dir=-1;
pre.time=0;
q.push(pre);
while(!q.empty())
{
pre=q.top();
q.pop();
if(pre.x==n-1&&pre.y==m-1) //判断是否达到了目标点;
{
printf("It takes %d seconds to reach the target position.\n",pre.time);
return;
}
//T 2:该处的剪枝
if(vis[pre.x][pre.y]!=0&&pre.time>vis[pre.x][pre.y])
/*如果当前坐标被访问过而且当前的时间大于vis中储存的时间,就放过这一个元素,进行下一个出队操作 ; */
continue;
for(i=0;i<4;i++) //4个方向的移动;
{
nxt.x=pre.x+dirs[i][0];
nxt.y=pre.y+dirs[i][1];
nxt.dir=i;
nxt.time=pre.time;
if(nxt.x<0||nxt.y<0||nxt.x>=n||nxt.y>=m) //判断是否越界;
continue;
if(maps[nxt.x][nxt.y]=='X') //判断改点是否是通路;
continue;
if(maps[nxt.x][nxt.y]=='.')
nxt.time++;
else
{ // T 3:加 1
nxt.time=nxt.time+maps[nxt.x][nxt.y]-'0'+1;
//注意此时别忘了加 1 ,
}
// T 4:剪枝--入队的判定
if(vis[nxt.x][nxt.y]==0||vis[nxt.x][nxt.y]>nxt.time) //如果此点未被访问过或者现在的时间小于或者等于曾经访问过的时间,入队;
{
vis[nxt.x][nxt.y]=nxt.time;
q.push(nxt);
}
}
}
printf("God please help our poor hero.\n");
}
int main()
{
int i,j;
while(scanf("%d%d",&n,&m)!=EOF)
//少了eof是会Output Limit Exceed
{
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
{
//scanf("%c",&maps[i][j]);
/*刚上来这里没用cin,用的是scanf,结合getchar(),但是最后依然输入有问题, 比如输入2 2,再按照题目格式输入{12(换行)34},执行下面的printf时输出的确实3,改为maps[1][0]后输出的确实一个负数,这一点有点不懂; */
cin>>maps[i][j];
}
getchar();
//这一句应该没有用了,刚上来是为了结合scanf吸收换行符用的
}
bfs();
//printf("%d",maps[1][1]-'0');测试
}
return 0;
}
T 3:对于此处要多加 1 ,是因为如果改点有数字,需要先加上该数字;再加 1 是因为从上一步移动到这一步本身就需要 1 秒 ;
T 4:剪枝,(1)如果此点已被它前面某一移动方式访问过 ,而且 此点所用的时间又大于前面的访问过的,那么它就要被舍弃,不能入队 因为前面某一点在接下来的移动过程是跟它完全一样的,而它的时间大于前面的,那么我们就没有让他入队的必要了;(2)同时这也 可以避免重复性移动,比如:“当前位置,记录时间--->向下移动,记录时间--->向上移动,记录时间 ”,这种移动又回到了原来 的 位置,但是时间增加了,因此舍弃该状态的入队;
T 2:剪枝, 在这一点处后来又被别的移动方式访问过,但是后来到达改点所用时间更少,而且两者在接下来的移动是完全一样的 ,后来的所用时间 更少,那么当前的时间多,就直接舍弃;
T 1:至于为什么使用优先队列,那是因为我们规定的方向 i 从 0 到 3 依次为下、上、左、右,那么在同一层次的移动方式中 总是下先入队,其他的后入队 想象这样一种结果:我们取出队首元素,但是这时候它的时间又大于新队首的 时间,恰好该状态满足条件到达目的地,那么我们就结束了操作; 然而新的队首元素加入出对也能满足条件,那么它的时间必定比之前的要小,所以我们输出的结果并不是真正的答案。使用优先队列则保证了每次出队的 为队列中所用时间最少的。