DP
解法:DP
维护 $f(t, i, j)$ 表示经过 $t$ 次传送后走到 $(i, j)$ 的最小代价。转移方程如下:
- 要么刚刚从一个更大数传送到 $(i, j)$,则 $f(t, i, j) \xleftarrow{\min} f(t - 1, i', j')$,其中
grd[i'][j'] >= grid[i][j]。 - 要么通过普通移动走到 $(i, j)$,则 $f(t, i, j) \xleftarrow{\min} \min(f(t, i - 1, j), f(t, i, j - 1)) + a_{i, j}$。
需要注意两点:
- 直接计算第一个转移方程的复杂度是 $\mathcal{O}((nm)^2)$ 的,我们可以将所有格子从大到小排序,这样就能用前缀 min 快速计算。
- 第一个转移方程要在第二个转移方程之前计算,因为第二个转移方程可能会用到第一个转移方程的结果。
答案就是 $\min\limits_{0 \le t \le k} f(t, n - 1, m - 1)$,即枚举具体传送了几次。复杂度 $\mathcal{O}(nm\log nm + nmk)$。
参考代码(c++)
class Solution {
public:
int minCost(vector<vector<int>>& grid, int K) {
int n = grid.size(), m = grid[0].size();
const long long INF = 1e18;
long long f[K + 1][n][m];
for (int k = 0; k <= K; k++) for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) f[k][i][j] = INF;
// 初值计算:不经过任何传送,走到 (i, j) 的最小代价
f[0][0][0] = 0;
for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) {
if (i + 1 < n) f[0][i + 1][j] = min(f[0][i + 1][j], f[0][i][j] + grid[i + 1][j]);
if (j + 1 < m) f[0][i][j + 1] = min(f[0][i][j + 1], f[0][i][j] + grid[i][j + 1]);
}
typedef pair<int, int> pii;
// 把格子按值分类
map<int, vector<pii>> mp;
for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) mp[-grid[i][j]].push_back({i, j});
// 枚举传送次数
for (int k = 1; k <= K; k++) {
long long mn = INF;
// 计算第一个转移方程,按值从大到小枚举格子
for (auto &p : mp) {
// 更新前缀 min
for (pii pos : p.second) mn = min(mn, f[k - 1][pos.first][pos.second]);
for (pii pos : p.second) f[k][pos.first][pos.second] = mn;
}
// 计算第二个转移方程
for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) {
if (i > 0) f[k][i][j] = min(f[k][i][j], f[k][i - 1][j] + grid[i][j]);
if (j > 0) f[k][i][j] = min(f[k][i][j], f[k][i][j - 1] + grid[i][j]);
}
}
long long ans = INF;
for (int k = 0; k <= K; k++) ans = min(ans, f[k][n - 1][m - 1]);
return ans;
}
};