参考博客
https://blog.csdn.net/qq_34542903/article/details/51100015
题目描述
机器人要在一个矩形迷宫里行动(不能原地停留,只能走向上/下/左/右),每移动一格花费1个单位时间。
迷宫有以下几种元素: 【*】 机器人的起点 【#】 墙。机器人不能走过这些格子 【.】 平地。
机器人可以在上面自由行走 【0-9】 宝藏。当机器人走到此处会立刻获得该数字相应的宝藏,宝藏不会消失,可以反复获取(但不能停留) 若机器人要恰好获得总和为x的宝藏,它最少需要多少时间?
输入要求
第一行输入任务数量T, 接下来有T个任务 每块第一行有两个整数, n( < 100), m( < 100), 表示迷宫有n+1行和m+1列,输出迷宫地图。 最后一行输入你要收集的宝藏的总价值x(x ≤ 100)
输出要求
对于每个任务,输出最少花费的时间,如果完成不了该任务则输出-1
输入样例
3
2 3
1.#2
#..#
*.#.
3
2 3
2.#2
#..#
*.#.
5
2 3
2.#2
#.3#
*.#.
5
输出样例
8
-1
6
思路分析
类似走迷宫的搜索问题,对于数据较大的一般用BFS解决
难点是需要在收集相应数目的金币的同时要求所走步数最小
可以这样理解,在地图上每走一步都有相应的值对应,可走的道路可以看作值为0的金币堆
通过深度搜索的形式不断向外可走的地方探寻搜索,直到达到目标金币数
因此需要一个记录金币数的数组,即可通过一个三维数组实现记录当前位置的同时记录当前金币数
需要注意的有两点
1.在搜索往下进行时,如果此时拥有的金币数大于目标金币数,应该结束继续往下的搜索,减少不必要的时间消耗(剪枝)。
2.同走迷宫不同,对于会重复经过同一位置的情况,需要多加一组判断,如果达到下一步的消耗的步数大于此时位置的步数+1,则走这一步,并将之推入队列。即通过当前位置走到下一步的位置是步数最短的情况。
AC代码
#include<stdio.h>
#include<string.h>
#include<queue>
#define Inf 99999
#define MAX 110
using namespace std;
int dir[4][2] = {0,-1,0,1,-1,0,1,0};//方向变换
char Map[MAX][MAX];//记录地图
int Step[MAX][MAX][MAX];//记录相应位置收集到不同金币数所花费的步数,除起始点外,其他位置初始为无穷大步
int column,row;//行、列
int target;//目标钱数
typedef struct
{
int px;
int py;
int money;
}Pt;//位置坐标
queue<Pt>que;//位置队列
int BFS()
{
int i;
Pt Bg;
Pt Next;
while(!que.empty())
{
Pt Bg = que.front();//读取队列最上方元素,即当前所处位置
que.pop();//将其移出队列
for(i = 0; i < 4; i++)
{
Next.px = Bg.px + dir[i][0];
Next.py = Bg.py + dir[i][1];//转向后位置坐标
if(Next.px >= 0&&Next.px <= row && Next.py >= 0&& Next.py <= column&&Map[Next.px][Next.py] != '#')
{
Next.money = Bg.money + Map[Next.px][Next.py] - '0';//转向后钱数为:之前所拥有钱数+当前位置钱数
if(Next.money == target)//如果走一步后达到目标钱数
return Step[Bg.px][Bg.py][Bg.money] + 1;//返回当前步数+1
if(Next.money > target)//如果下一步得到的钱数过多,跳过向这边搜索(剪枝),继续循环
continue;
if(Step[Next.px][Next.py][Next.money] > Step[Bg.px][Bg.py][Bg.money] + 1)//如果原来的步数+1小于下一步所花的步数
{
Step[Next.px][Next.py][Next.money] = Step[Bg.px][Bg.py][Bg.money] + 1;//将原步数+1赋值给下一步中
que.push(Next);//将下一步推入队列
}
}
}
}
return -1;//未找到方法得到所求钱数
}
int main()
{
int T,i,j,k;
Pt start;
scanf("%d",&T);
while(T--)
{
while(!que.empty())//将队列中现存的元素全部推出
{
que.pop();
}
scanf("%d %d",&row,&column);
memset(Map,0,sizeof(Map));
for(i = 0; i <= row; i++)
scanf("%s",Map[i]);
scanf("%d",&target);
for(i = 0; i <= row; i++)
for(j = 0; j <= column; j++)
for(k = 0; k <= target; k++ )
Step[i][j][k] = Inf;//给步数赋初值为无穷大
for(i = 0; i <= row; i++)//重新初始化地图,记录起始点,对于每一位置赋值钱数
for(j = 0; j <= column; j++)
{
if(Map[i][j] == '*')//起始点
{
Map[i][j] = '0';//起始点可通过,获得钱数为0
start.px = i;
start.py = j;//记录起始位置
start.money = 0;//记录起始钱数
Step[i][j][0] = 0;//当前步数为0
que.push(start);//将起始点推入队列
}
if(Map[i][j] == '.')
{
Map[i][j] = '0';//'.'为可通过,经过此位置得到钱数为0
}
}
if(target == 0)//特例判断,当target = 0时会返回-1,在这里特判一下
printf("0\n");
else
printf("%d\n",BFS());
}
return 0;
}
总结和分析
有时候思路不需要太过复杂,多看多学,考虑以空间换时间。
第一次思考的时候是这样想的
1.用BFS求出起点到各宝藏点的距离
2.宝藏点到其他宝藏点的距离
3.从起点开始DP
结果事实证明错的离谱,需要注意和学习的是要有数据化题目的能力。
主要就是一点没看透:"."所代表的通路其实就是金币数为0的位置。