洛谷P2802 回家-最新题解

洛谷P2802 回家-最新题解

写这篇题解的原因:水经验 洛谷方面增加了#11号数据,导致之前的题解代码都无法AC。

题目传送门
来看这个题解,应该都看过题了,下面水字数的粘贴可以直接跳过!

小H在一个划分成了n*m个方格的长方形封锁线上。 每次他能向上下左右四个方向移动一格(当然小H不可以静止不动),
但不能离开封锁线,否则就被打死了。 刚开始时他有满血6点,每移动一格他要消耗1点血量。一旦小H的 血量降到 0, 他将死去。
他可以沿路通过拾取鼠标(什么鬼。。。)来补满血量。只要他走到有鼠标的格子,他不需要任何时间即可拾取。格子上的鼠标可以瞬间补满,所以每次经过这个格子都有鼠标。就算到了某个有鼠标的格子才死去,
他也不能通过拾取鼠标补满 HP。 即使在家门口死去, 他也不能算完成任务回到家中。

地图上有 5 种格子:

数字 0: 障碍物。

数字 1: 空地, 小H可以自由行走。

数字 2: 小H出发点, 也是一片空地。

数字 3: 小H的家。

数字 4: 有鼠标在上面的空地。

小H能否安全回家?如果能, 最短需要多长时间呢?
输入格式
第一行两个整数n,m, 表示地图的大小为n*m。
下面 n 行, 每行 m 个数字来描述地图。
输出格式
一行, 若小H不能回家, 输出-1,否则输出他回家所需最短时间。
输入样例:
3 3
2 1 1
1 1 0
1 1 3
输出样例:
4

这个题呢…很常规的DFS。
遍历所有状态,找到“回家”的最短时间(路线)。
但…为了防止TLE,要进行剪枝,就是用一个bool或者你想用啥用啥,能够记录某个点是否使用过就可!避免重复性操作!

九十分代码:

#include
#include
using namespace std;

int n, m,ans=999999;//n,m是地图的范围,ans最优答案
int map[150][150],book[150][150];//map存储地图信息,book标记某点是否走过
int _next[4][2] = {
      {
     0,1},{
     1,0},{
     0,-1},{
     -1,0} };//存储往 上 下 左 右 走的坐标变化
#define min(x,y) (x>y?y:x)
void dfs(int hp, int i, int j, int _time)//hp 走到(i,j)点的血量 _time走到(i,j)点用的时间
{
     
	if (hp == 0 || map[i][j] == 0||book[i][j]) return;
	//如果hp=0,map[i][j]=0(出界或者该点为障碍物),book[i][j]==1 该点走过,返回
	if (map[i][j] == 3)//到家,更新ans
	{
     
		ans = min(ans, _time);
		return;
	}
	if (map[i][j] == 4) hp = 6;//捡到血包
	for (int z = 0; z < 4; z++) {
     //通过循环,实现上下左右移动
		book[i][j] = 1;//标记使用
		dfs(hp - 1, i + _next[z][0], j + _next[z][1], _time + 1);
		book[i][j] = 0;//释放该点
	}
	return;
}

int main()
{
     
	while (cin >> n >> m)
	{
     
		ans = 999999;
		memset(map, 0, sizeof(map));
		pair<int, int> strat;
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
			{
     
				cin >> map[i][j];
				if (map[i][j] == 2) strat.first = i, strat.second = j;//记录起始点
			}
		dfs(6, strat.first, strat.second, 0);
		if (ans == 999999) cout << -1 << endl;//如果ans没有发生更新,则无法到达
		else cout << ans << endl;
	}
	return 0;
}

上面代码可以通过以前的十个数据,但新增加的11号数据无法通过!
我们先来看一下十一号数据:

输入
7 6
2 0 0 0 0 0
1 0 0 0 0 0
1 1 4 0 0 0
1 0 0 0 0 0
1 1 1 1 1 3
4 0 1 0 4 0
0 0 4 0 0 0
输出:
15

这组数据有什么特别之处呢?
手动走一下就会发现,通过(6,1)(6,5)两处的加血,可以长续航的到达终点,这就要求出现**(5,1)->(6,1)->(5,1)**->(5,2)->…这种重复性路线。
那么这种情况就与我们book的作用冲突!
如果我们去掉book呢?
洛谷P2802 回家-最新题解_第1张图片
实践证明:重复性操作是不能容忍的!
因此,我们接下来要考虑,如何剪掉重复性操作的同时,把“能量供给”操作给保留!
在这方面思考,就有了接下来的100分代码。

#include
#include
using namespace std;

int n, m,ans=999999;
int map[150][150];
pair<int,int>book[150][150];//第一个int 装该点有没有走过   第二个int 如果该点走过,走过时的hp为多少
int _next[4][2] = {
      {
     0,1},{
     1,0},{
     0,-1},{
     -1,0} };
#define min(x,y) (x>y?y:x)
void dfs(int hp, int i, int j, int _time)
{
     
	if (hp == 0 || map[i][j] == 0) return;
	if (book[i][j].first==1&&book[i][j].second > hp) return;
	//如果该点走过,且这一次走过时hp小于之前走过时hp,表明没有被补给!那么就属于违法操作,return掉
	//如果first=0->没有走过,second经过补给操作,那么该点就是合法点
	if (map[i][j] == 3)
	{
     
		ans = min(ans, _time);
		return;
	}
	if (map[i][j] == 4)  hp = 6;

	for (int z = 0; z < 4; z++) {
     
		book[i][j].first = 1, book[i][j].second = hp;//更新该点走过的状态
		dfs(hp - 1, i + _next[z][0], j + _next[z][1], _time + 1);
		book[i][j].first = 0, book[i][j].second = 0;//释放该点
	}
	return;
}

int main()
{
     
	while (cin >> n >> m)
	{
     
		ans = 999999;
		memset(map, 0, sizeof(map));
		pair<int, int> strat;
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
			{
     
				cin >> map[i][j];
				if (map[i][j] == 2) strat.first = i, strat.second = j;
			}
		dfs(6, strat.first, strat.second, 0);
		if (ans == 999999) cout << -1 << endl;
		else cout << ans << endl;
	}
	return 0;
}

小白码字不易,客官可否将小赞赞留下~
问题交流:[email protected]

你可能感兴趣的:(题解,dfs,剪枝)