这里附上题目链接:回家。
小H在一个划分成了n*m个方格的长方形封锁线上。 每次他能向上下左右四个方向移动一格(当然小H不可以静止不动), 但不能离开封锁线,否则就被打死了。 刚开始时他有满血6点,每移动一格他要消耗1点血量。一旦小H的 血量降到 0, 他将死去。 他可以沿路通过拾取鼠标(什么鬼。。。)来补满血量。只要他走到有鼠标的格子,他不需要任何时间即可拾取。格子上的鼠标可以瞬间补满,所以每次经过这个格子都有鼠标。就算到了某个有鼠标的格子才死去, 他也不能通过拾取鼠标补满 HP。 即使在家门口死去, 他也不能算完成任务回到家中。
地图上有 5 种格子:
数字 0: 障碍物。
数字 1: 空地, 小H可以自由行走。
数字 2: 小H出发点, 也是一片空地。
数字 3: 小H的家。
数字 4: 有鼠标在上面的空地。
小H能否安全回家?如果能, 最短需要多长时间呢?
第一行两个整数n,m, 表示地图的大小为n*m。
以下 n 行, 每行 m 个数字来描述地图。
这是一道很显然的搜索题。但是有很多限制条件:血量、地图边界、障碍以及最小步数。
特别的是,每一可行的格是可以被重复遍历的。因为在某些地图中,小H能够到达家但又面临血量不够的情况从而需要重复遍历某个含有鼠标的格子来补满血量。
本题有两种解法。
在题意转换中已提到,某些格可能会被重复遍历,所以只建立一个标记数组来记录每格的遍历情况显然是不够的。因为一个标记数组只能保证每格最多被遍历一次。
因为能够遍历一格是有很多前提条件的,为了尽可能减少时间,所以需要进行剪枝(可行性剪枝与最优性剪枝)。
可行性剪枝:当前格的坐标不能超出地图边界(因为每到一格又需要分别遍历四个方向上的格子)、当前血量不能为0、当前格不能为障碍;
最优性剪枝:(建立两个数组分别记录之前到达该格时小H拥有的最高血量与最小步数)如果到达某格时,小H的血量①不高于之前到达此格时的血量且②步数不低于之前到达此格时所用的步数,即可断定小H此时的走法一定不是最优解,即刻终止对此格的遍历。
①②两条只要有一条不满足,都不能说明小H此时的走法一定不是最优解。同时替换两个数组分别记录的最高血量与最小步数。
满足所有前提条件后,可以遍历该格。
若当前格有鼠标,则补满小H血量;
若当前格已是家,说明小H已经到达终点,不用再走了;
若当前格不是家,则分别遍历四个方向上的格子。
最后输出结果(-1或最小步数)。-1说明小H不能到达家。
AC代码
#include
#include
int recent_hp[9][9],recent_step[9][9];//记录之前到达该格时小H拥有的最高血量与最小步数
int map[9][9],n,m;//map为地图数组
int min_step=100,home_x,home_y;//min_step为最少步数,home_x为家横坐标
int location[4][2]={{-1,0},{1,0},{0,1},{0,-1}};//四个方位
int check(int H_x,int H_y)
{
if(map[H_x][H_y]==0||H_x>=n||H_x<0||H_y>=m||H_y<0)//障碍、坐标越界
{
return 0;//返回假值
}
return 1;//返回真值
}
int DFS(int H_x,int H_y,int hp,int step)//H_x为小H当前行坐标,hp为当前生命值,step为当前步数
{
int i;
//可行性剪枝
if(!check(H_x,H_y))//若坐标不符要求
{
return 0;
}
if(hp==0)//生命值为0
{
return 0;
}
//最优性剪枝
if(hp<=recent_hp[H_x][H_y]&&step>=recent_step[H_x][H_y])
{
return 0;
}
//满足所有前提条件后替换两个数组分别记录的最高血量与最小步数
recent_hp[H_x][H_y]=hp;
recent_step[H_x][H_y]=step;
if(map[H_x][H_y]==3)//生命值大于0且到达家
{
if(step<min_step)//当前步数小于之前找到的最小步数
{
min_step=step;//进行替换
}
return 1;
}
//若当前格有鼠标,则补满血量
if(map[H_x][H_y]==4)
{
hp=6;
}
//若当前格不是家
for(i=0;i<4;i++)
{
//分别遍历四个方位;
DFS(H_x+location[i][0],H_y+location[i][1],hp-1,step+1);
}
return 0;
}
int main()
{
int i,j,x,y;
//输入数据
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
{
scanf("%d",&map[i][j]);
if(map[i][j]==2)
{
x=i;
y=j;
}
}
}
//遍历
DFS(x,y,6,0);
//输出结果
if(min_step==100)//若不能到达家
{
printf("-1");
}
else
{
printf("%d\n",min_step);
}
return 0;
}
刚开始,要先找到小H的出发点坐标。
关键思路和前述方法一致:
如果到达某格时,小H的血量①不高于之前到达此格时的血量且②步数不低于之前到达此格时所用的步数,即可断定小H此时的走法一定不是最优解,那么就不将该格加入队列。
AC代码
#include
#include
struct point
{
int x;//行坐标
int y;//列坐标
int hp;//
int step;//从出发点到达该点的步数
}queue[82];
int map[9][12];//map地图
int n,m,min_step,sign=0;//地图有n行m列
int location[4][2]={{-1,0},{1,0},{0,-1},{0,1}};//4个方位
int recnt_step[9][9],recnt_hp[9][9];
//recnt_hp用于记录最近经过该格时小H的最优血量
//recnt_step用于记录最近经过该格时小H的最优步数
int check(int x,int y)//检查
{
if(x>=0&&x<n&&y>=0&&y<m&&map[x][y])//坐标合法且该格不为障碍
{
return 1;
}
return 0;
}
int BFS(int home_x,int home_y)
{
int i,x,y;
int head=0,tail=0;//定义队首、队尾
//从队尾入队
queue[tail].x=home_x;
queue[tail].y=home_y;
queue[tail].hp=6;//刚开始血量为6
tail++;
while(head<tail&&!sign)//在队列未空时
{
if(queue[head].hp>1)//当血量大于1
{
for(i=0;i<4&&!sign;i++)//尝试遍历四个方向
{
//计算坐标
x=queue[head].x+location[i][0];
y=queue[head].y+location[i][1];
if(!check(x,y))//若坐标不合格
{
continue;//直接遍历下一个方向
}
if(recnt_hp[x][y]>=queue[head].hp-1&&recnt_step[x][y]<=queue[head].step+1)//若小H走到该格时,血量不高于前一次并且步数不低于前一次,则说明小H此时的路线一定不是最优解
{
continue;
}
else
{
//新点从队尾入队
queue[tail].x=x;
queue[tail].y=y;
//更新各项指标
queue[tail].step=queue[head].step+1;//从队长到达相邻的格子需要多走1步
queue[tail].hp=map[x][y]==4? 6:queue[head].hp-1;//从队长到达相邻的格子需要多消耗1点血
recnt_hp[x][y]=queue[tail].hp;//更新最优血量
recnt_step[x][y]=queue[tail].step;
if(map[x][y]==3)//若找到家
{
sign=1;
min_step=queue[tail].step;//记录最小步数
}
tail++;
}
}
}
head++;//将队员全部入队的旧队长出队
}
return 0;
}
int main()
{
int i,j;
int home_x,home_y;
//输入数据并搜索出发位置
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
{
scanf("%d",&map[i][j]);
if(map[i][j]==2)//找到出发位置
{
home_x=i;
home_y=j;
}
}
}
//广搜
BFS(home_x,home_y);
//输出结果
if(sign)//若找到家
{
printf("%d",min_step);
}
else
{
printf("-1");
}
return 0;
}