OpenJudge-021:鸣人和佐助
题目描述:
题目传送门:添加链接描述
已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置。地图上的每个位置都可以走到,只不过有些位置上有大蛇丸的手下,需要先打败大蛇丸的手下才能到这些位置。鸣人有一定数量的查克拉,每一个单位的查克拉可以打败一个大蛇丸的手下。假设鸣人可以往上下左右四个方向移动,每移动一个距离需要花费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
解题思路
0.默认你已经学会了BFS才来看这个题解。
1.读完题目,可以发现这道题本质上还是用BFS来算迷宫中的最短距离的题目,每次移动一个格子需要一个单位时间,打败大蛇丸的手下不用时间,因此总的移动的格数就是所需的最短时间,问题得到了转换。
2. 这道题比较麻烦的地方在于如果碰到大蛇丸的手下,需要使用查克拉才可以通过。按题目的说法,大蛇丸的手下应该是可以在据点不断产生,打败通过以后,下次再次来到的时候,仍然需要打败。
3. 因此,普通的BFS如果要进行剪枝的话,就是说走过的路标记起来就可以,走到有标记的地方不再继续,也就是不走老路,因为之前已经探索过了,后来再探索肯定跟之前走的重复了,而后来才到说明这条路肯定不如之前的短,BFS都是一层一层地往下走的。
4. 但是这题不能如此标记,因为之前探索过的地方,后面再次访问到可能是因为之前到达该点的时候鸣人身上的道具不足以让它继续下去,因此才在BFS后面的层到达这个点。
5. 那么该如何剪枝呢?我们来想象这样的一个点,它的左右下都是大蛇丸手下,但是往上面是通路,那么BFS如果先往上再往下走,绝对构不成最短路,这样走出来的路,鸣人身上的查克拉跟先前一样。因此,可以说只要到达某个点,且发现先前到过的时候,要对两次鸣人身上的查克拉的数量进行比较,来决定是否剪枝。
6. 如果第二次到的时候比先前到时候查拉斯多,则有可能构成最优解。但是,如果反而少了的话,肯定不如之前那样好,也可以这么理解,反正地图始终是不变的,那么我后来到,时间成本已经比之前用的多了,现在连查拉斯也不如之前的多,肯定不是最优解,直接剪枝即可。
7. 然后,为了让代码简洁好看,我们弄一个三维的bool数组,前两维记载地点,最后一维记录查斯拉的量,BFS的时候,如果地点,查斯拉的量发现与之前的某次一样,肯定要剪枝,因为虽然查斯拉成本一样,但是时间成本已经不如上一次好了。
8.何时判定到不了佐助位置?如果无法在队列循环中找到就说明没有通路。此时输出-1。
上代码
#include<iostream>
#include<queue>
using namespace std;
int m,n,t;//代表m行n列的地图和鸣人初始的查克拉数量t
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
//后期用一个for(int i=0;i<4;i++)
//就可以让本来的x,y有dx,dy的增量
//例如x+dx[0],y+dy[0]的时候,相当于往下走了一行.
char grid[200][200];
bool visit[200][200][15];
int startx,starty;//鸣人的坐标.
//判断是否遍历过的数组.
struct point
{
point()
{}
point(int x_,int y_,int matter_,int time_)
{
x=x_;
y=y_;
matter=matter_;
time=time_;
}//构造函数.
int x;//x坐标.
int y;//y坐标.
int matter;//道具剩余量.
int time;//当前时间多少了.
};
void init()
{
cin>>m>>n>>t;
for(int i=1;i<=m;i++)
//0行0列那行不存数据,更符合生活中的习惯.
for(int j=1;j<=n;j++)
{
cin>>grid[i][j];
if(grid[i][j]=='@')
{
startx=i;
starty=j;
grid[i][j]='*';
//鸣人最初待着的地方其实也是通路.
//起点存储起来以后该点就是通路.
}//找起始点.
}
}
void BFS()
{
queue<point>q;
q.push(point(startx,starty,t,0));//将起始点放入队列.
visit[startx][starty][t]=1;
while(q.size())
{
point tmp=q.front();
q.pop();
if(grid[tmp.x][tmp.y]=='+')
{
cout<<tmp.time<<endl;
return;
}//已经找到佐助,就结束函数.
for(int i=0;i<4;i++)
{
int newx=tmp.x+dx[i];
int newy=tmp.y+dy[i];
if(!(newx>=1&&newx<=m&&newy>=1&&newy<=n))
continue;//超了地图范围,绝对不可以.
if(grid[newx][newy]=='#')
{
if(tmp.matter==0)
continue;
if(visit[newx][newy][tmp.matter-1]==0)
{
q.push(point(newx,newy,tmp.matter-1,tmp.time+1));
visit[newx][newy][tmp.matter-1]=1;
}
}
if(grid[newx][newy]=='+'||grid[newx][newy]=='*')
{
if(visit[newx][newy][tmp.matter]==0)
{
q.push(point(newx,newy,tmp.matter,tmp.time+1));
visit[newx][newy][tmp.matter]=1;
}
}
}
}
cout<<-1<<endl;
}
int main()
{
init();//初始化.
BFS();//BFS.
}