P9351 [JOI 2023 Final] Maze 题解

Description

给定一张 R×CR\times C 的地图,其中 . 可以走,而 # 不能走。一次操作可以将 N×NN \times N 的正方形范围内所有点变成 .,给定起点和终点,求最少需要几次操作使得起点和终点连通(只能上下左右移动)。

R×C6×106R\times C\le 6\times 10^6NRCN\le R\le C

Solution

先考虑怎么暴力求出答案。

假设当前从起点走到了 (x,y)(x,y),那么 (x,y)(x,y) 此时一定为白点,下一步可以花 00 的代价走到一个初始就是白色的点。

或者花费 11 的代价在 (x,y)(x,y) 周围涂白一个大小为 NN 的正方形并走到这个正方形里的任何一个点 (x0,y0)(x_0,y_0),容易发现 (x,y)(x,y) 能走到 (x0,y0)(x_0,y_0) 当且仅当 xx0,yy0N|x-x_0|,|y-y_0|\leq Nmin{xx0,yy0}N1\min\left\{|x-x_0|,|y-y_0|\right\}\leq N-1

建图跑 01bfs 可以得到一个 O(RCN2)O(RCN^2) 的做法。过不了。

由于算法慢在二操作的边数过多,所以考虑把二操作的长距离走法优化成多次走相邻格子的过程。

因为 (x,y)(x,y) 能走到 (x0,y0)(x_0,y_0) 当且仅当 xx0,yy0N|x-x_0|,|y-y_0|\leq Nmin{xx0,yy0}N1\min\left\{|x-x_0|,|y-y_0|\right\}\leq N-1,所以 (x,y)(x0,y0)(x,y)\to (x_0,y_0) 可以看成先走到四联通格子,再走至多 N1N-1 步八联通。

于是把步数作为第一关键字,走的八联通步数看作第二关键字跑最短路即可。

dijkstra 直接做可以做到 O(RClogRC)O(RC\log RC),但是可能能用 01bfs 做到 O(RC)O(RC)

时间复杂度:O(RClogRC)O(RC\log RC)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxT = 6e6 + 5, kMaxN = 3e3 + 5;
const int kD4[][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
const int kD8[][2] = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}, {0, 1}, {0, -1}, {1, 0}, {-1, 0}};

int n, m, k;
int sx, sy, tx, ty;
std::vector<int> G[kMaxT];
std::string s[kMaxN];

void dijkstra() {
std::vector<std::vector<std::pair<int, int>>> dis(n + 1, std::vector<std::pair<int, int>>(m + 1));
std::vector<std::vector<bool>> vis(n + 1, std::vector<bool>(m + 1));
std::priority_queue<std::tuple<int, int, int, int>> q;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
dis[i][j] = {1e9, 1e9};
dis[sx][sy] = {0, k - 1};
q.emplace(0, -(k - 1), sx, sy);
for (; !q.empty();) {
auto [d, w, x, y] = q.top();
q.pop();
if (vis[x][y]) continue;
vis[x][y] = 1;
for (auto [dx, dy] : kD4) {
int tx = x + dx, ty = y + dy;
if (tx < 1 || tx > n || ty < 1 || ty > m) continue;
if (s[tx][ty] == '.') {
std::pair<int, int> p = {dis[x][y].first, k - 1};
if (dis[tx][ty] > p) {
dis[tx][ty] = p;
q.emplace(-p.first, -p.second, tx, ty);
}
}
std::pair<int, int> p = {dis[x][y].first + 1, 0};
if (dis[tx][ty] > p) {
dis[tx][ty] = p;
q.emplace(-p.first, -p.second, tx, ty);
}
}
if (dis[x][y].second < k - 1) {
for (auto [dx, dy] : kD8) {
int tx = x + dx, ty = y + dy;
if (tx < 1 || tx > n || ty < 1 || ty > m) continue;
std::pair<int, int> p = {dis[x][y].first, dis[x][y].second + 1};
if (dis[tx][ty] > p) {
dis[tx][ty] = p;
q.emplace(-p.first, -p.second, tx, ty);
}
}
}
}
std::cout << dis[tx][ty].first << '\n';
}

void dickdreamer() {
std::cin >> n >> m >> k >> sx >> sy >> tx >> ty;
for (int i = 1; i <= n; ++i) {
std::cin >> s[i];
s[i] = " " + s[i];
}
dijkstra();
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P9351 [JOI 2023 Final] Maze 题解
https://sobaliuziao.github.io/2024/10/18/post/7ab25385.html
作者
Egg_laying_master
发布于
2024年10月18日
许可协议