傳送門
Dijkstra
這個題可以直接用 Dijkstra 來寫。
A*
但還可以用 A* 來寫。
就不深入學習 A* 了,下面只簡單說說他的幾個概念。
A* 的原理
跟 Dijkstra 着實很像。Dijkstra 每次選擇一個估價函數最小的沒有作爲過出發點的結點,並用它去更新其他結點。這裏,Dijkstra 的估價函數是起點到該結點的最短距離。注意,已經證明用這種方式去更新是保證正確的(保證求出來的是最短路),因此你儘管用。
A* 每次選擇一個估價函數最小的沒有作爲過出發點的結點,並用它去更新其他結點。這裏,A* 的估價函數是一個 。注意,已經證明用下面說的方式寫出來的 是保證正確的(保證求出來的是最短路),因此你儘管用。很像吧。
A* 的
設 表示從起點到結點 的最短路距離。我們需要再寫一個 ,然後令:
便得到了適用於 A* 的估價函數。
顯然,當 時,這就是一個 Dijkstra,很簡單吧?
A* 的
已經證明:
- 如果 ,其中 表示從 結點到終點的實際距離,則能得到最優解。
- 如果 ,那麼搜索將嚴格沿着最短路徑進行。
- 如果 ,不能保證得到最優解。
直觀地說,當 越大,但又不超過 時,搜索時就更具方向性。讓更有可能成爲最短路上的點先出列,當然會更快。正確性?已經有人證明,我不管了。
另外需要注意, 時,不能保證得到最優解,但可能得到近似解。具體效率取決於你的 怎麼寫。(比如你寫 怎麼辦?我不敢保證,我也不研究了)
於拯救行動
這道題的話,一個可行的能得到最優解的 顯然可以是當前點到終點的曼哈頓距離,因爲無論是遇到了牆堵路還是遇到了守衛堵路都會使得 。
用 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;
}
參考資料
只寫我複製了字的資料: