【HDU 1242】搜索 | DFS | BFS | 迷宫 | CGWR | N

                                               Rescue

                         Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
                                         Total Submission(s): 36678    Accepted Submission(s): 12701

URL

http://acm.hdu.edu.cn/showproblem.php?pid=1242

Introduction

迷宫题,上下左右移动,每次移动消耗 1 单位时间,'.' 是路,'#' 是墙,唯一的 'a' 是起点,任意一个 'r' 是终点,'x' 代表守卫,经过守卫格会额外消耗 1 单位时间。输入迷宫矩阵,若能自起点到达终点则输出最短耗时,否则输出扑街。

Input

多组,读至EOF。每组第一行是两个整数 R C,代表行数、列数,接下来 R 行是 R 个长度为 C 的字符串(不含空格的字符串),代表整个迷宫矩阵。(矩阵字符含义详见上文 Introduction 

Output

若能自起点到达终点则输出最短耗时带回车,否则输出 "Poor ANGEL has to stay in the prison all his life." 。

Sample Input

7 8

#.#####.

#.a#..r.

#..#x..

..#..#.#

#...##..

.#......

........

Sample Output

13

Analysis   &   AC code

本题是经典的迷宫搜索题,DFS 与 BFS 都可解决本题,但由于本题最短时的特点 ,BFS 的速度将优于 DFS。

由于起点唯一,而终点可能有多个,故只可从起点开始搜索。

下面对 DFS 和 BFS 分别进行分析。

DFS:

  • 暴力搜索,需要寻找所有可能的路径,并更新最短时
    • 1. 初始化:vis数组清0
    • 2. 搜索至终点时:更新最短时(全局变量)
    • 3. 剪枝:if 当前时间 >= 当前最短时,进行剪枝
    • 4. 回溯:使用 “vis标记1 --> 递归 --> vis标记0” 进行回溯搜索

以下是DFS代码:

/** HDU 1242 DFS迷宫
 *  by Kevin.
 */
#include <cstdio>
#include <cstring>
#define M 223
#define visit(x) vis[x.r][x.c]=1
#define notVis(x) !vis[x.r][x.c]
#define valid(x) x.r>=0&&x.c>=0&&x.r<R&&x.c<C&&a[x.r][x.c]!='#'

using namespace std;

char a[M][M];
char vis[M][M];
int R, C, r1, c1, minTime;

const int dr[] = {-1,1,0,0};
const int dc[] = {0,0,-1,1};

void DFS(int r, int c, int time)
{
	if(a[r][c]=='r')
	{
		if(time<minTime)
			minTime = time;
		return;
	}
	if(time>=minTime)
		return;

	int rr, cc, i, ntime;
	for(i=0; i<4; i++)
	{
		rr = r+dr[i];
		cc = c+dc[i];
		if(a[rr][cc]!='#' && !vis[rr][cc] && rr>=0 && rr<R && cc>=0 && cc<C)
		{
			if(a[rr][cc]=='x')
				ntime = time+2;
			else
				ntime = time+1;
			char temp = a[rr][cc];
			vis[rr][cc] = 1;
			DFS(rr, cc, ntime);
			vis[rr][cc] = 0;        // 回溯
		}
	}
}

int main()
{
	int r, c;
	while(scanf("%d %d",&R,&C)!=EOF)
	{
		memset(vis, 0, sizeof(vis));
		minTime = 0xFFFF;
		for(r=0; r<R; r++)
		{
			for(c=0; c<C; c++)
			{
				scanf(" %c",a[r]+c);
				if(a[r][c] == 'a')
					r1 = r, c1 = c;
			}
		}
		vis[r1][c1] = 1;
		DFS(r1, c1, 0);
		if(minTime!=0xFFFF)
			printf("%d\n",minTime);
		else
			puts("Poor ANGEL has to stay in the prison all his life.");
	}
	return 0;
}

BFS:

  • 普通队列实现的BFS:
    • 1. 初始化:
      • vis数组清0,head tail 队列指针置 0
      • 标记 vis 起点为1
      • 起点入队
    • 2. 对出队结点的操作:
      • 如果为终点,则说明找到了最短时路径,结束搜索
      • 如果为守卫点,则将此点更改为普通路径点,并将耗时++,然后再次入队(此次入队可不必再次标记 vis),代表进行了一次战斗
      • 注意:此题非常特殊,因为 战斗时间 == 移动时间,故仍可使用普通队列进行BFS。但如果是 != 则只能选用优先队列,否则将会打乱队列内部严格的时间增序!

      • 如果为普通路径点或起点,则试探邻接的四个点,若未访问过且不为墙结点,则访问之并将耗时++,然后入队。

以下是使用普通队列的BFS代码: 

/** HDU 1242 BFS迷宫 普通队列实现
 *  by Kevin.
 */
#include <cstdio>
#include <cstring>
#define M 223
#define visit(x) vis[x.r][x.c]=1
#define notVis(x) !vis[x.r][x.c]
#define valid(p) p.r>=0&&p.c>=0&&p.r<R&&p.c<C&&a[p.r][p.c]!='#'

using namespace std;

struct Point
{
	int r, c, time;
	Point(void) { }
	Point(int rr, int cc) : r(rr), c(cc) { }
};

Point q[10*M], src;

char a[M][M];
char vis[M][M];
int R, C, minTime;

const int dr[] = {-1,1,0,0};
const int dc[] = {0,0,-1,1};

bool BFS()
{
	bool out = false;
	Point now, next;
	int head = 0, tail = 0;
	visit(src);
	q[tail++] = src;

	while(head!=tail)
	{
		now = q[head++];
		switch(a[now.r][now.c])
		{
			case'r':
				out = true;
				minTime = now.time;
				break;
			case'x':
				++now.time;
				a[now.r][now.c] = '.';
				q[tail++] = now;            // 此次入队可不必再次标记 vis
				break;
			default:
				++now.time;
				for(int i=0; i<4; ++i)
				{
					next = now;
					next.r += dr[i];
					next.c += dc[i];
					if(valid(next) && notVis(next))
					{
						visit(next);
						q[tail++] = next;
					}
				}
				break;
		}
		if(out)
			break;
	}
	return out;
}

int main()
{
	int r, c;
	while(~scanf("%d %d", &R, &C))
	{
		memset(vis, 0, sizeof(vis));
		for(r=0; r<R; r++)
		{
			for(c=0; c<C; c++)
			{
				scanf(" %c",a[r]+c);
				if(a[r][c] == 'a')
					src.r = r, src.c = c;
			}
		}
		src.time = 0;
		if(BFS())
			printf("%d\n", minTime);
		else
			puts("Poor ANGEL has to stay in the prison all his life.");
	}
	return 0;
}
  • 优先队列实现的BFS:

【CGWR】在 HDU 本题的 discuss 区有很多小伙伴反映自己的优先队列 BFS 怎么也过不去,在分析了他们的代码之后,博主发现此题有一个 巨坑 ,下面进行分析

  • 首先是【错误示例】
    • 1. 初始化:
      • vis数组清0
      • 标记 vis 起点为1
      • 起点入队
    • 2. 对出队结点的操作:
      • 如果为终点,则说明找到了最短时路径,结束搜索
      • 如果为普通路径点或起点,则试探邻接的四个点,若未访问过且不为墙结点,则访问之并将耗时++,然后入队。
      • 如果为守卫点,也试探上下左右四个点,若未访问过且不为墙结点,则访问之并将耗时 +=2,然后入队。

我们把普通队列实现的 BFS 代码中的 case 'x'  进行修改,便得到以下【错误示例代码】

/** HDU 1242 BFS迷宫错误示例 优先队列实现
 *  by Kevin.
 */
#include <cstdio>
#include <cstring>
#include <queue>
#define M 223
#define visit(x) vis[x.r][x.c]=1
#define notVis(x) !vis[x.r][x.c]
#define valid(p) p.r>=0&&p.c>=0&&p.r<R&&p.c<C&&a[p.r][p.c]!='#'

using namespace std;

struct Point
{
	int r, c, time;
	Point(void) { }
	Point(int rr, int cc) : r(rr), c(cc) { }

	inline bool operator < (const Point &ot) const
	{
		return time > ot.time;
	}
};

Point src;

char a[M][M];
char vis[M][M];
int R, C, minTime;

const int dr[] = {-1,1,0,0};
const int dc[] = {0,0,-1,1};

bool BFS()
{
	bool out = false;
	priority_queue<Point> pq;
	Point now, next;
	visit(src);
	pq.push(src); 

	while(!pq.empty())
	{
		now = pq.top();pq.pop();
		switch(a[now.r][now.c])
		{
			case'r':
				out = true;
				minTime = now.time;
				break;
			case'x':                    // 此处进行修改
				now.time+=2;
				for(int i=0; i<4; ++i)
				{
					next = now;
					next.r += dr[i];
					next.c += dc[i];
					if(valid(next) && notVis(next))
					{
						visit(next);
						pq.push(next); 
					}
				}
				break;
			default:
				++now.time;
				for(int i=0; i<4; ++i)
				{
					next = now;
					next.r += dr[i];
					next.c += dc[i];
					if(valid(next) && notVis(next))
					{
						visit(next);
						pq.push(next); 
					}
				}
				break;
		}
		if(out)
			break;
	}
	return out;
}

int main()
{
	int r, c;
	while(~scanf("%d %d", &R, &C))
	{
		memset(vis, 0, sizeof(vis));
		for(r=0; r<R; r++)
		{
			for(c=0; c<C; c++)
			{
				scanf(" %c",a[r]+c);
				if(a[r][c] == 'a')
					src.r = r, src.c = c;
			}
		}
		src.time = 0;
		if(BFS())
			printf("%d\n", minTime);
		else
			puts("Poor ANGEL has to stay in the prison all his life.");
	}
	return 0;
}

为什么会错呢?这里 错就错在 :此代码是在向上下左右扩展结点之前,就根据当前点是否为守卫点决定此次扩展后新入队结点的 time 是 += 1 还是 += 2。而正确的做法是 :在向上下左右扩展结点的过程中,根据当前点的扩展点是否为守卫点决定此次扩展后新入队结点的 time 是 += 1 还是 += 2。

一言以蔽之,就是 time += 2 应该发生在即将扩展至守卫点时,而不是守卫点出队后。

例:

x   r

a   .

显然答案应该是 2 ,然而按照上面的【错误示例代码】却输出了3

为什么 r 的 time 会更新为 3 呢?我们来看一下到底发生了什么:

  • 首先 a 入队,a 的 time 是 0
  • 然后 a 出队,按照【上、下、左、右】的顺序,将 x 和 . 入队,由于在这份代码中 time += 2 发生在守卫点出队后,因此它们的 time 都是 1 (因为当前出队的是 a )
  • 然后 x 出队,按照【上、下、左、右】的顺序,将 r 入队,由于在这份代码中 time += 2 发生在守卫点出队后,因此 r 的 time 是 3 (因为当前出队的是 x )
  • 然后 . 出队,由于 r 已经被访问且时间已经被设定为 3,故此时无法再使 r 重新入队,尽管现在产生的 r 的 time 更小

正是由于 time += 2 的发生时间 不正确,导致优先队列没有发挥出 “ 优先 ” 的作用。

解决方法有二:

  • 1. 不修改time += 2 的发生时间,将 vis 数组更改为 int 类型,仍初始化为0,其某点的值设定为到此点的最小 time。如果 time 不为 0 的话就入队,否则将当前 time 与已有 time 进行比较,如果更小则更新最小值。
  • 2. 修改time += 2 的发生时间 为 即将扩展至守卫点时,这样优先队列仍然能发挥出 “ 优先 ” 的作用。

下面贴出 方法 2 的 AC代码:

/** HDU 1242 BFS迷宫AC代码 优先队列实现
 *  by Kevin.
 */
#include <cstdio>
#include <cstring>
#include <queue>
#define M 223
#define visit(x) vis[x.r][x.c]=1
#define notVis(x) !vis[x.r][x.c]
#define valid(x) x.r>=0&&x.c>=0&&x.r<R&&x.c<C&&a[x.r][x.c]!='#'

using namespace std;

struct Point
{
	int r, c, time;
	Point(void) { }
	Point(int rr, int cc) : r(rr), c(cc) { }

	inline bool operator < (const Point &ot) const
	{
		return time > ot.time;
	}
};

Point src;

char a[M][M];
char vis[M][M];
int R, C, minTime;

const int dr[] = {-1,1,0,0};
const int dc[] = {0,0,-1,1};

bool BFS()
{
	priority_queue<Point> pq;
	Point now, next;
	src.time = 0;
	visit(src);
	pq.push(src);

	while(!pq.empty())
	{
		now = pq.top();pq.pop();
		for(int i=0; i<4; ++i)
		{
			next.time = now.time+1;
			next.r = now.r + dr[i];
			next.c = now.c + dc[i];
			if(valid(next) && notVis(next))
			{
				visit(next);
				switch(a[next.r][next.c])
				{
					case'r':
						minTime = next.time;
						return true;
					case'x':
						++next.time;
						pq.push(next);
						break;
					default:
						pq.push(next);
						break;
				}
			}
		}
	}
	return false;
}

int main()
{
	int r, c;
	while(~scanf("%d %d", &R, &C))
	{
		memset(vis, 0, sizeof(vis));
		for(r=0; r<R; r++)
		{
			for(c=0; c<C; c++)
			{
				scanf(" %c",a[r]+c);
				if(a[r][c] == 'a')
					src.r = r, src.c = c;
			}
		}
		src.time = 0;
		if(BFS())
			printf("%d\n", minTime);
		else
			puts("Poor ANGEL has to stay in the prison all his life.");
	}
	return 0;
}

 

猜你喜欢

转载自blog.csdn.net/weixin_42102584/article/details/81253244
今日推荐