0
点赞
收藏
分享

微信扫一扫

专题一 简单搜索

耶也夜 2022-01-10 阅读 51
c++

L:鸣人和佐助   BFS
Description
佐助被大蛇丸诱骗走了,鸣人在多少时间内能追上他呢?
已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置。地图上的每个位置都可以走到,只不过有些位置上有大蛇丸的手下,需要先打败大蛇丸的手下才能到这些位置。鸣人有一定数量的查克拉,每一个单位的查克拉可以打败一个大蛇丸的手下。假设鸣人可以往上下左右四个方向移动,每移动一个距离需要花费1个单位时间,打败大蛇丸的手下不需要时间。如果鸣人查克拉消耗完了,则只可以走到没有大蛇丸手下的位置,不可以再移动到有大蛇丸手下的位置。佐助在此期间不移动,大蛇丸的手下也不移动。请问,鸣人要追上佐助最少需要花费多少时间?

Input
输入的第一行包含三个整数:M,N,T。代表M行N列的地图和鸣人初始的查克拉数量T。0 < M,N < 200,0 ≤ T < 10。后面是M行N列的地图,其中@代表鸣人,+代表佐助。*代表通路,#代表大蛇丸的手下。
Output
输出包含一个整数R,代表鸣人追上佐助最少需要花费的时间。如果鸣人无法追上佐助,则输出-1。

Sample Input
样例输入1

4 4 1
#@##
**##
###+
****
1
2
3
4
5
样例输入2

4 4 2
#@##
**##
###+
****
1
2
3
4
5
Sample Output
样例输出1

6
1
样例输出2

4
1
题目分析
这道题目因为是求最短路径(时间),我们还是选用广度优先搜索。但是不一样的在于队列的结构体中除了坐标(x,y)和时间(tot),还增加了当前的查克拉数量(T)。现在我们开始分析过程——
1. 定义变量,避免重复走过路径(awl)
2. 输入,与其他迷宫题目输入相同,需要保存起点
3. 进入广度优先搜索
4. 遍历4个方向
5. 判断边界和重复。这是一个难点——通常的迷宫问题,我们会使用二维的bool数组来判断重复,甚至直接改变原来的地图——但是这道题就会有BUG,给出一个数据(默认优先向下行走,坐标从(0,0)开始):

5 5 2
@#***
####*
#**#*
#****
+####
 

这道题目,首先看到最短时间,肯定想到BFS,但是这题与普通的BFS不一样,普通的BFS标记了访问过的点,能搜出一条最短路径。但此处不能再用二维标记访问过的点了,因为被标记过的点不能再访问了,而此处很有可能再访问

以这个例子为例,先去掉查克拉这个因素,看成普通宽搜,我们知道,宽搜为了找到一条最短路, 会去标记访问的哪些点

(1,1)——>(1,2)——>(1,3)。。。会把(1, 3)给标记了,但是这条路走下去发现走不通,由于查克拉不足等因素。

于是另一条正确的路(1,1)——>(2,1)——>(3,1)——>(3,2)——>(3,3)——>(2,3)——>(1,3)中也来到了(1, 3)这时(1, 3)也被标记过了,无法再访问,输出了-1

如何解决上述问题:

1.把st标记数组直接去掉不用可以吗?

答:不可以,会超内存

这是超内存的错误代码,用的仍然是二维标记数组

#include <iostream>
#include <cstring>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=205;
int px,py;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1}; 
char map[N][N];
int dis[N][N];  //去掉了dis的标记功能
struct node
{
   int x,y;
   int co;	
}q[N*N*N];
int n,m,t;
int bfs(int x,int y)
{
	int hh=0,tt=-1;
	//memset(dis,-1,sizeof dis);
	dis[x][y]=0;
    q[++tt]={x,y,t};
	
	while(hh<=tt)
	{
		
	struct node t=q[hh++];
	if(map[t.x][t.y]=='+')
	{
		return dis[t.x][t.y];
	}
	
	
	for(int i=0;i<4;i++)
	{
		int xx=t.x+dx[i];
		int yy=t.y+dy[i];
		if(xx>=0&&xx<m&&yy>=0&&yy<n)//无标记数组查重
		{
			
			if(map[xx][yy]=='#')
			{
				if(t.co-1>=0)
				{
				dis[xx][yy]=dis[t.x][t.y]+1;
				q[++tt]={xx,yy,t.co-1};
			//	cout<<"xx is "<<xx<<" yy is "<<yy<<endl;
			    }
			    else continue;
			}
			else
			{
				dis[xx][yy]=dis[t.x][t.y]+1;
				q[++tt]={xx,yy,t.co};
			//	cout<<"xx is "<<xx<<" yy is "<<yy<<endl;
			}
		}
	}
   }
   
   return -1;
}
int main()
{
	while(cin>>m>>n>>t)
	{
		for(int i=0;i<m;i++)
		for(int j=0;j<n;j++)
		{
		cin>>map[i][j];
		if(map[i][j]=='@')
		{
			px=i;
			py=j;
		}
	    }
	    
	   cout<<bfs(px,py)<<endl;
	}
	
	return 0;
}

2.正确做法是什么?

用一个三维数组,前两维与正常的坐标对应,最后一维用来记录这个点剩余的查克拉数量,这样重复经过某个点,只要他们剩余的查克拉数量不一样,他们就是彼此独立的,就像之前举的那两个路径的例子

#include <iostream>
#include <cstring>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=205;
int px,py;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1}; 
char map[N][N];
bool st[N][N][N];   //定义三重标记
struct node
{
   int x,y;
   int co;
   int step;	
}q[N*N];
int n,m,t;
int bfs(int x,int y)
{
	memset(st,false,sizeof st);
	int hh=0,tt=-1;
	st[x][y][0]=true;
    q[++tt]={x,y,t,0};
	
	while(hh<=tt)
	{
		
	struct node t=q[hh++];
	if(map[t.x][t.y]=='+')
	{
		return t.step;
	}
	
	
	for(int i=0;i<4;i++)
	{
		int xx=t.x+dx[i];
		int yy=t.y+dy[i];
		if(xx>=0&&xx<m&&yy>=0&&yy<n)
		{
			
			if(map[xx][yy]=='#')
			{
				if(t.co-1>=0)
				{
				 if(!st[xx][yy][t.co-1]) 
				 {
				    q[++tt]={xx,yy,t.co-1,t.step+1};
			        //	cout<<"xx is "<<xx<<" yy is "<<yy<<endl;
			        st[xx][yy][t.co-1]=true;
		         }
			    }
			    else continue;
			}
			else
			{
				if(!st[xx][yy][t.co])
				{
				st[xx][yy][t.co]=true;
				q[++tt]={xx,yy,t.co,t.step+1};
			//	cout<<"xx is "<<xx<<" yy is "<<yy<<endl;
	        	}
			}
		}
	}
   }
   
   return -1;
}
int main()
{
	while(cin>>m>>n>>t)
	{
		for(int i=0;i<m;i++)
		for(int j=0;j<n;j++)
		{
		cin>>map[i][j];
		if(map[i][j]=='@')
		{
			px=i;
			py=j;
		}
	    }
	    
	   cout<<bfs(px,py)<<endl;
	}
	
	return 0;
}

举报

相关推荐

0 条评论