题解:P10988 [蓝桥杯 2023 国 Python A] 走方格

· · 题解

前言

水一发理论复杂度比 bfs 低但常数超大,比 bfs 还慢的解法。(可能是假的,因为算法标签没有标)

思路

首先,我们发现小人只能往下走,不能往上走。这让我联想到了P7074 方格取数。和本题也有异曲同工之妙,因为那道题只能往右走,不能往左走。

因为“反复横跳”是没有意义的(也就是不优的),所以我们可以不考虑往右走完又往回走的情况。

所以,考虑从上往下 DP。设 f_{i,j} 表示走到 ij 列最小代价。有如下转移式:

- 当 $k<j$ 时,$a_{i, k} < a_{i, k + 1} < \dots < a_{i, j}$; - 当 $k>j$ 时,$a_{i, k} < a_{i, k - 1} < \dots < a_{i, j}$。 转移式的第一部分是从左边来,第二部分是从上面来,第三部分统称是快速移动。 但这还是 $O(n^3)$ 的怎么办? 你看,这个转移式的瓶颈在枚举 $k$ 上。然鹅其实不必那么麻烦,直接拿一个数据结构:线段树! 她支持动态单点修改和区间求最小值,她再合适不过了! ~~线段树 /qq~~ 所以时间复杂度降到了 $O(n^2 \log n)$。~~虽然这不是最优解但是这是复杂度最优的做法~~ ## 代码 欢迎 Hack 或指出错误或证伪。 ```cpp #include <bits/stdc++.h> using namespace std; const int N = 1e3 + 50; int n; int a[N][N], f[N][N]; int l[N], r[N]; int t[4 * N]; void buildtree(int k, int l, int r) { if (l == r) { t[k] = 1 << 30; return; } int m = (l + r) / 2; buildtree(k + k, l, m); buildtree(k + k + 1, m + 1, r); t[k] = min(t[k + k], t[k + k + 1]); } void update(int k, int l, int r, int x, int y, int z) { if (l == x && r == y) { t[k] = min(t[k], z); return; } int m = (l + r) / 2; if (y <= m) update(k + k, l, m, x, y, z); else if (x > m) update(k + k + 1, m + 1, r, x, y, z); else update(k + k, l, m, x, m, z), update(k + k + 1, m + 1, r, m + 1, y, z); t[k] = min(t[k + k], t[k + k + 1]); } int calc(int k, int l, int r, int x, int y) { if (l == x && r == y) return t[k]; int m = (l + r) / 2; if (y <= m) return calc(k + k, l, m, x, y); else if (x > m) return calc(k + k + 1, m + 1, r, x, y); else return min(calc(k + k, l, m, x, m), calc(k + k + 1, m + 1, r, m + 1, y)); } // 以上是线段树模板 int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) scanf("%d", &a[i][j]); int x; x = 1; f[1][1] = 0; for (int i = 2; i <= n; i++) { if (a[1][i] >= a[1][i - 1]) x = i; f[1][i] = f[1][i - 1] + 1; if (x != i) f[1][i] = min(f[1][i], f[1][x] + 1); } for (int i = 2; i <= n; i++) { buildtree(1, 1, n); //memset(l, 0, sizeof(l)); l[1] = f[i - 1][1] + 1; update(1, 1, n, 1, 1, l[1]); x = 1; for (int j = 2; j <= n; j++) { if (a[i][j] >= a[i][j - 1]) x = j; l[j] = min(f[i - 1][j] + 1, l[j - 1] + 1); if (x != j) l[j] = min(l[j], calc(1, 1, n, x, j - 1) + 1); update(1, 1, n, j, j, l[j]); } // 从左面来 buildtree(1, 1, n); //memset(r, 0, sizeof(r)); r[n] = f[i - 1][n] + 1; update(1, 1, n, n, n, r[n]); x = n; for (int j = n - 1; j; j--) { if (a[i][j] >= a[i][j + 1]) x = j; r[j] = f[i - 1][j] + 1; if (x != j) r[j] = min(r[j], calc(1, 1, n, j + 1, x) + 1); update(1, 1, n, j, j, r[j]); } // 从右面来 f[i][1] = min({l[1], r[1], f[i - 1][1] + 1}); for (int j = 2; j <= n; j++) f[i][j] = min({f[i - 1][j] + 1, f[i][j - 1] + 1, l[j], r[j]}); // 更新 dp 数组 } printf("%d\n", f[n][n]); return 0; } ``` ~~没卡常你们卡去吧~~