题解:P12684 【MX-J15-T4】叉叉学习魔法

· · 题解

## 题意 有一个 $n\times m$ 的地图,其中 `.` 表示空地,`#` 表示墙,`X` 表示起点,`W` 表示终点。你可以从空地 $(i,j)$ 走一步到上/下/左/右的空地,也可以使用一次魔法,从空地 $(i,j)$ 走到左上/左下/右上/右下的空地而**不耗费步数**。从起点走到终点,你需要在最小化步数的前提下最小化使用魔法的次数,或报告无解。$2\leq n,m\leq 5\times 10^3$。 ## 题解 用状态 $(x,y,t_1,t_2)$ 表示走到 $(x,y)$ 处耗费了 $t_1$ 步,使用了 $t_2$ 次魔法。容易想到用以 $t_1$ 为第一关键字、$t_2$ 为第二关键字的小优先队列维护最优状态,然后跑 BFS。这样子时间复杂度是 $\mathcal{O}(nm\log{(nm)})$ 的,可以获得 $75$ 分。 考虑优化。我们发现每次状态转移时,要么给 $t_1$ 加 $1$,要么给 $t_2$ 加 $1$,考虑 0/1 BFS,也就是用双端队列替代优先队列。最直接的想法就是给 $t_1$ 加 $1$ 的状态加到队尾,给 $t_2$ 加 $1$ 的状态加到队头。由于每次取出的状态是单增的,不难证明其正确性。 但我们一写,发现它 WA 了。 问题在哪儿?我们发现队列中的状态是**不严格单调递增**的,取出最优状态 $(x,y,t_1,t_2)$ 后,队头可能还有 $(x',y',t_1,t_2)$ 的状态,如果我们此时就把 $(x,y,t_1,t_2+1)$ 加到队头,单调性就被破坏了。 所以我们每次把 $t_1$ 和 $t_2$ 都相同的状态全部取出,然后再加入所有新的状态即可。 时间复杂度 $\mathcal{O}(nm)$。 ## 代码 ```cpp #include <iostream> #include <queue> #include <deque> using namespace std; #define lowbit(x) ((x) & -(x)) #define chk_min(x, v) (x) = min((x), (v)) #define chk_max(x, v) (x) = max((x), (v)) typedef long long ll; typedef pair<int, int> pii; const int N = 5e3 + 5; const int dx1[] = {-1, 1, 0, 0}, dy1[] = {0, 0, -1, 1}; const int dx2[] = {-1, -1, 1, 1}, dy2[] = {-1, 1, -1, 1}; int n, m, x1, y1, x2, y2; char a[N][N]; bool vis[N][N]; struct Node { int x, y, t1, t2; bool operator<(const Node &x) const { if (t1 != x.t1) return t1 > x.t1; return t2 > x.t2; } }; deque<Node> q; vector<Node> v1, v2; inline void calc() { q.push_front({x1, y1, 0, 0}); while (!q.empty()) { Node cur = q.front(); while (!q.empty() && cur.t1 == q.front().t1 && cur.t2 == q.front().t2) { cur = q.front(); q.pop_front(); if (cur.x == x2 && cur.y == y2) { cout << cur.t1 << ' ' << cur.t2 << '\n'; return; } if (vis[cur.x][cur.y]) continue; vis[cur.x][cur.y] = 1; for (int i = 0; i < 4; ++i) { int x = cur.x + dx1[i], y = cur.y + dy1[i]; if (!x || !y || x == n + 1 || y == m + 1 || a[x][y] == '#') continue; v1.push_back({x, y, cur.t1 + 1, cur.t2}); } for (int i = 0; i < 4; ++i) { int x = cur.x + dx2[i], y = cur.y + dy2[i]; if (!x || !y || x == n + 1 || y == m + 1 || a[x][y] == '#') continue; v2.push_back({x, y, cur.t1, cur.t2 + 1}); } } for (Node st : v2) q.push_front(st); for (Node st : v1) q.push_back(st); v1.clear(), v2.clear(); } cout << "-1 -1\n"; } int main() { ios::sync_with_stdio(0), cin.tie(0); cin >> n >> m; for (int i = 1; i <= n; ++i) { cin >> a[i] + 1; for (int j = 1; j <= m; ++j) if (a[i][j] == 'X') x1 = i, y1 = j; else if (a[i][j] == 'W') x2 = i, y2 = j; } calc(); return 0; } ```