百练 4116 拯救行动 Dijkstra / A*

传送门
Dijkstra

这个题可以直接用 Dijkstra 来写。

A*

但还可以用 A* 来写。

就不深入学习 A* 了,下面只简单说说他的几个概念。

A* 的原理

跟 Dijkstra 着实很像。Dijkstra 每次选择一个估价函数最小的没有作为过出发点的结点,并用它去更新其他结点。这里,Dijkstra 的估价函数是起点到该结点的最短距离。注意,已经证明用这种方式去更新是保证正确的(保证求出来的是最短路),因此你尽管用。

A* 每次选择一个估价函数最小的没有作为过出发点的结点,并用它去更新其他结点。这里,A* 的估价函数是一个 ff。注意,已经证明用下面说的方式写出来的 ff 是保证正确的(保证求出来的是最短路),因此你尽管用。很像吧。

A* 的 ff

g(x)g(x) 表示从起点到结点 xx 的最短路距离。我们需要再写一个 h(x)h(x),然后令:
f(x)=g(x)+h(x) f(x) = g(x) + h(x)

便得到了适用于 A* 的估价函数。

显然,当 h(x)0h(x) \equiv 0 时,这就是一个 Dijkstra,很简单吧?

A* 的 hh

已经证明

  1. 如果 h(x)<d(x)h(x) < d(x),其中 d(x)d(x) 表示从 xx 结点到终点的实际距离,则能得到最优解。
  2. 如果 h(x)=d(x)h(x) = d(x),那么搜索将严格沿着最短路径进行。
  3. 如果 h(x)>d(x)h(x) > d(x),不能保证得到最优解。

直观地说,当 h(x)h(x) 越大,但又不超过 d(x)d(x) 时,搜索时就更具方向性。让更有可能成为最短路上的点先出列,当然会更快。正确性?已经有人证明,我不管了。

另外需要注意,h(x)>d(x)h(x) > d(x) 时,不能保证得到最优解,但可能得到近似解。具体效率取决于你的 hh 怎么写。(比如你写 h(x)=105x2h(x) = 10^5 - x^2 怎么办?我不敢保证,我也不研究了)

hh 于拯救行动

这道题的话,一个可行的能得到最优解的 hh 显然可以是当前点到终点的曼哈顿距离,因为无论是遇到了墙堵路还是遇到了守卫堵路都会使得 d(x)>h(x)d(x) > h(x)

用 A*,实测效率确实比用 Dijkstra 高了。赞!

参考代码

代码中还出现了两个概念:open(表)和 close(表)。怎么用的、什么意思,我不管了。用 Dijkstra 类比:close 表就是已经刷新过别的点的那些结点,open 表就是可以拿去刷新别的点的那些结点。Dijkstra 中为什么没有 close 表?因为 Dijkstra 用另一种方式代替了 close 表的存在(见我其他 Dijkstra 的代码),A* 中能这么写吗?我不敢保证,所以就用一个 close 表吧。

对了,代码中的 isInOpen 数组是不必要的,删除即可。

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <iostream>
#include <queue>

constexpr int maxn = 205;
int n, m;
char map[maxn][maxn];
int sx, sy, ex, ey;

int isInOpen[maxn][maxn];
int isInClose[maxn][maxn];
struct Node
{
	int x, y;
	int dis, pri;
	Node() = default;
	Node(int x, int y, int dis, int pri) :
		x(x), y(y), dis(dis), pri(pri) {}

	bool operator<(const Node& b) const
	{
		return pri > b.pri;
	}
};
void AStar()
{
	for (int i = 0; i < n; i++)
		std::memset(isInOpen[i], 0, sizeof(isInOpen[0][0]) * m);
	for (int i = 0; i < n; i++)
		std::memset(isInClose[i], 0, sizeof(isInClose[0][0]) * m);

	std::priority_queue<Node> open;
	open.push(Node(sx, sy, 0, 0));
	isInOpen[sx][sy] = true;
	while (!open.empty())
	{
		auto from = open.top(); open.pop();
		if (!isInOpen[from.x][from.y])
			continue;
		isInOpen[from.x][from.y] = false;
		isInClose[from.x][from.y] = true;
		if (from.x == ex && from.y == ey)
		{
			printf("%d\n", from.dis);
			return;
		}
		const int vecx[]{ 1, -1, 0, 0 };
		const int vecy[]{ 0, 0, 1, -1 };
		for (int i = 0; i < 4; i++)
		{
			int newx = from.x + vecx[i];
			int newy = from.y + vecy[i];
			if (0 <= newx && newx < n && 0 <= newy && newy < m && map[newx][newy] != '#')
			{
				if (!isInClose[newx][newy])
				{
					open.push(Node(newx, newy, from.dis + 1 + (map[newx][newy] == 'x'),
						[&](int x, int y, int g) -> int
						{
							int h = std::abs(ex - x) + std::abs(ey - y);
							return g + h; // Dijkstra: h = 0
						}(newx, newy, from.dis + 1 + (map[newx][newy] == 'x'))));
					isInOpen[newx][newy] = true;
				}
			}
		}
	}
	puts("Impossible");
}

int main()
{
	int o;
	std::cin >> o;
	while (o--)
	{
		std::cin >> n >> m;
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
			{
				char ch = std::getchar();
				while (ch != '@' && ch != 'a' && ch != 'r' && ch != 'x' && ch != '#')
					ch = std::getchar();
				map[i][j] = ch;
				if (ch == 'r')
				{
					sx = i;
					sy = j;
				}
				else if (ch == 'a')
				{
					ex = i;
					ey = j;
				}
			}

		AStar();
	}
	return 0;
}
参考资料

只写我复制了字的资料:

  1. A* 算法.百度百科
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章