题目相关
题目链接
计蒜客 OJ,https://nanti.jisuanke.com/t/T1214。
题目描述
已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置。地图上的每个位置都可以走到,只不过有些位置上有大蛇丸的手下,需要先打败大蛇丸的手下才能到这些位置。鸣人有一定数量的查克拉,每一个单位的查克拉可以打败一个大蛇丸的手下。假设鸣人可以往上下左右四个方向移动,每移动一个距离需要花费 1 个单位时间,打败大蛇丸的手下不需要时间。如果鸣人查克拉消耗完了,则只可以走到没有大蛇丸手下的位置,不可以再移动到有大蛇丸手下的位置。佐助在此期间不移动,大蛇丸的手下也不移动。请问,鸣人要追上佐助最少需要花费多少时间?
输入格式
输入的第一行包含三个整数:M,N,T。代表 M 行 N 列的地图和鸣人初始的查克拉数量 T。0<M,N<200,0≤T<10
后面是 M 行 N 列的地图,其中 @ 代表鸣人,+ 代表佐助。* 代表通路,# 代表大蛇丸的手下。
输出格式
输出包含一个整数 R,代表鸣人追上佐助最少需要花费的时间。如果鸣人无法追上佐助,则输出 −1。
样例输入1
4 4 1
#@##
**##
###+
****
样例输出1
6
样例输入2
4 4 2
#@##
**##
###+
****
样例输出2
4
题目分析
题意分析
一个 M*N 大小的迷宫,我们从 @ 位置出发(也就是起点),字符 * 表示可以安全通行的方格,字符 # 表示有怪物(可以杀死,但是需要付出代价),字符 * 表示仙药(也就是终点)。要求输出从 @ 到 * 的最短路径。那么迷宫问题的基本要素全齐了,所以本题就是一道 BFS 模板题。
和上一题,https://blog.csdn.net/justidle/article/details/104662118,最大的不同是在于:杀死大蛇丸的手下是需要付出代价的,也就是所谓的查克拉。而题目提供的查克拉是有限的。因此本题最大的变化就是如何表示查克拉的变化。
对于 # 表示的地点,我们有两种操作。第一种,查克拉够,我们可以杀死大蛇丸的手下,通过这个地方。第二种查克拉不够,那么意味着这里不能通过,需要绕道。如何表示查克拉,是本题最大的变化。因为某个位置导致查克拉变化后,其后所有状态都会发生改变。所以我们将 visit 数组从二维变为三维,也就是最后一维表示不同查克拉状态的访问性。
样例数据分析
如果想看类似的数据分析,可以看以前的文章,https://blog.csdn.net/justidle/article/details/104651311。主要是我偷懒了,画图太累了,请原谅。
算法思路
1、读入数据,并写入到合适的数据结构中。
2、找到起点位置,将起点加入到队列 q 中。
3、记录终点位置信息。
4、开始 BFS 遍历。直到找到终点或者遍历所有节点而无法到达终点。碰到 # 的道路,需要判断查克拉。
AC 参考代码
#include <cstdio>
#include <queue>
struct POS {
int x, y;//坐标
int t;//查克拉数
int dis;//距离
};
const int MAXN = 202;
const int MAXT = 10;
struct MAZE {
int row, col;
int t;
char data[MAXN][MAXN];//迷宫数据
bool visit[MAXN][MAXN][MAXT];//访问性
int x1, y1;
int x2, y2;
};
int bfs(MAZE &maze);
int main() {
MAZE maze = {};//迷宫定义
//读入迷宫长宽
scanf("%d %d %d", &maze.row, &maze.col, &maze.t);
//读入迷宫数据
int i,j;
for (i=0; i<maze.row; i++) {
for (j=0; j<maze.col; j++) {
scanf(" %c", &maze.data[i][j]);
if (maze.data[i][j]=='+') {
//终点
maze.x2 = i;
maze.y2 = j;
} else if (maze.data[i][j]=='@') {
//起点
maze.x1 = i;
maze.y1 = j;
}
}
}
int ans = bfs(maze);
printf("%d\n", ans);
return 0;
}
int bfs(MAZE &maze) {
std::queue<POS> q;
const POS move[] = {{-1,0}, {0,1}, {1,0}, {0,-1}};
POS cur, next;
//加入起点
cur.x = maze.x1;
cur.y = maze.y1;
cur.dis = 0;
cur.t = maze.t;
q.push(cur);
maze.visit[cur.x][cur.y][cur.t] = true;
//开始遍历
while (!q.empty()) {
cur = q.front();
q.pop();
for (int i=0; i<4; i++) {
next.x = cur.x + move[i].x;
next.y = cur.y + move[i].y;
//判断是不是终点
if (next.x==maze.x2 && next.y==maze.y2) {
return cur.dis+1;
}
//判断通过性
if (next.x>=0&&next.x<maze.row&&next.y>=0&&next.y<maze.col) {
if (maze.data[next.x][next.y]!='#') {
//通路或者佐助
if (maze.visit[next.x][next.y][cur.t]==false) {
next.t = cur.t;
next.dis = cur.dis + 1;
q.push(next);
maze.visit[next.x][next.y][next.t] = true;
}
} else {
//大蛇丸的手下
if (cur.t > 0 && maze.visit[next.x][next.y][cur.t-1]==false) {
next.t = cur.t - 1;
next.dis = cur.dis + 1;
q.push(next);
maze.visit[next.x][next.y][next.t] = true;
}
}
}
}
}
return -1;
}
代码分析
1、由于有查克拉的存在,每个结点需要增加查克拉信息记录。代码部分如下:
struct POS {
int x, y;//坐标
int t;//查克拉数
int dis;//距离
};
2、关于 # 的处理。注意代码中对通过性的判断。以前题目中,我们是将本节点能否走和其他条件一起判断。这里,我们需要分开判断,先判断其他条件,再判断是否为大蛇丸的手下(即 #)。代码部分如下:
if (maze.data[next.x][next.y]!='#') {
不是大蛇丸的手下,那么操作和以前一样
} else {
大蛇丸手下
先判断查克拉是否够用 cur.t>0
再判断查克拉减 1 后是否访问过 maze.visit[next.x][next.y][cur.t-1]==false
}