Problem
佐助被大蛇丸诱骗走了,鸣人在多少时间内能追上他呢?
已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置。地图上的每个位置都可以走到,只不过有些位置上有大蛇丸的手下,需要先打败大蛇丸的手下才能到这些位置。鸣人有一定数量的查克拉,每一个单位的查克拉可以打败一个大蛇丸的手下。假设鸣人可以往上下左右四个方向移动,每移动一个距离需要花费1个单位时间,打败大蛇丸的手下不需要时间。如果鸣人查克拉消耗完了,则只可以走到没有大蛇丸手下的位置,不可以再移动到有大蛇丸手下的位置。佐助在此期间不移动,大蛇丸的手下也不移动。请问,鸣人要追上佐助最少需要花费多少时间?
Input
输入的第一行包含三个整数:M,N,T。代表M行N列的地图和鸣人初始的查克拉数量T。
0 < M,N < 200,0 ≤ T < 10
后面是M行N列的地图,其中@代表鸣人,+代表佐助。*代表通路,#代表大蛇丸的手下。
Output
输出包含一个整数R,代表鸣人追上佐助最少需要花费的时间。如果鸣人无法追上佐助,则输出-1。
SampleInput
样例输入1
4 4 1
#@##
**##
###+
样例输入2
4 4 2
#@##
**##
###+
SampleOutput
样例输出1
6
样例输出2
4
解题思路
题意:题目描述得很清楚
思路:
一开始直接用朴素bfs和增添一个新状态(克拉数)的结构体存储节点信息,真的让人wa到怀疑人生
正确的做法应当是这样:
还是二维bfs(我看到很多人增加成了三维 其实没有必要),用一个二维数组记录每个节点剩余的max查克拉数量,如果当前查克拉数量,小于等于之前经过该点时的查克拉数量,那就不用走这一步,否则,仍然可以继续走。
在这里我要提出两个关键问题:
问题1:
为什么同一点后面查克拉的数量还有可能大于前面的呢?
因为他是一个点向上下左右分别BFS搜索的,可能从左边走到某个点就要消耗1个查克拉,但是从下走到相同点的路途上并没有消耗查克拉,因此可能出现这种情况。
问题二2:
为什么后一次的查克拉如果大于前一次走到这的则可以放入队列呢?
走不同的路线,查克拉的剩余量可能是不一样的。上次走这的时候如果查克拉剩余是n,但是可能走另一条路再到这的时候是大于n的。
这时候到底要不要再把这个点放进去呢?答案是,如果上次的小于这次的剩余查克拉,则可以走,因为上次走的时候保存的信息在队列的前面,如果上次是能追到佐助的话,程序会在执行到后一次走这之前就结束了,如果没结束说明上次走这的时候,剩余的查克拉不够走到佐助,所以:后一次的查克拉如果大于前一次走到这的则可以放入队列。
根据这套逻辑写的一份还算简洁的代码:
#include<bits/stdc++.h>
using namespace std;
struct node//存储节点信息
{
int x,y,step,t;//横、纵坐标,步数,查克拉数
};
int m,n,T;
const int N = 210;
char g[N][N];
queue<node> q;
int sx,sy;
int chark[N][N];
int dx[]={1,0,-1,0},dy[]={0,1,0,-1};
int bfs()
{
while(!q.empty())
{
node tmp = q.front();
q.pop();
for(int i=0;i<4;++i)
{
int a = tmp.x+dx[i],b = tmp.y+dy[i];
int tim = tmp.step;
int c = tmp.t;
if(g[a][b]=='+') return tim+1;
if(a<0||a>=m||b<0||b>=n||chark[a][b]>=c) continue;
if(g[a][b]=='#')
{
if(c>0)
{
chark[a][b]=c-1;
q.push({a,b,tim+1,c-1});
}
continue;
}
chark[a][b]=c;
q.push({a,b,tim+1,c});
}
}
return -1;
}
int main()
{
cin>>m>>n>>T;
memset(chark,-1,sizeof chark);
for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j)
{
cin>>g[i][j];
if(g[i][j]=='@')
{
sx = i,sy = j;
chark[sx][sy]=T;
q.push({sx,sy,0,T});
}
}
}
cout<<bfs()<<endl;
return 0;
}