百練 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* 算法.百度百科
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章