题意:给定一个\(n*m\)的地图,每个格子有初始高度。只能向右或向下移动,且要求第\(i+1\)步的高度必须比第\(i\)步恰好高\(1\)。每次操作可以使得任意一个格子的高度减\(1\)。问最少需要几次操作,使得地图中存在由\((1,1)\)到\((n,m)\)的路径。
思路:
DP。
由贪心原理可以知道,满足题意的路径中一定经过一个不需要降低高度的格子。以这个格子的高度为基准,计算可能经过的格子的花销,更新dp数组。
令\(dp[i][j]\)表示从\((1,1)\)到\((i,j)\)所需要的最小操作数
转移方程:
\(dp[i][j]=min(dp[i-1][j]+cost,dp[i][j-1]+cost)\)
而这个花销可以这样计算:
因为只能向右或向下移动,说明相邻两步移动的坐标差恰为1,又高度差也恰为1,所以:设这个基准高度为\(H(x,y)\),由题可知\(H(x,y)\)与任意格子的应有高度\(H(i,j)\)存在以下关系:
\(H(i,j)=H(x,y)-(x-i+y-j)\)
所以做法是先通过遍历选定基准格子\((x,y)\),再计算得出可能路径上的格子的应有高度,据此再计算花销。
也可以通过\(H(x,y)\)计算\(H(1,1)\),再以\(H(1,1)\)计算各格子的应有高度,此时式子可简化为:
\(H(i,j)=H(1,1)+i+j-2\)
代码采用第二种方法。
typedef long long LL;
const LL INF = 1e18;
const int maxn = 100 + 10;
LL Map[maxn][maxn], dp[maxn][maxn];
int n, m;
LL solve(LL begin) {
//对于每一个不同的起点值,都要初始化dp数组
//注意从0开始初始化,否则更新dp[2][1]与dp[1][2]时会出错
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
dp[i][j] = INF;
}
}
dp[1][1] = Map[1][1] - begin;
//起点格子的花销
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 1 && j == 1) continue;
//dp[1][1]算过了,跳过
LL nowh = begin + i + j - 2;
//以标准高度为基准计算(i,j)格的应有高度
if (Map[i][j] < nowh) continue;
//实际高度比应有高度还小,没救了
LL cost = Map[i][j] - nowh;
//计算(i,j)格的花费
dp[i][j] = min(dp[i - 1][j] + cost, dp[i][j - 1] + cost);
}
}
return dp[n][m];
}
int main()
{
int t; cin >> t; while (t--) {
LL ans = INF;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> Map[i][j];
}
}
//尝试通过遍历所有可能的标准高度后计算得出起点值,再从这个起点值开始dp到终点
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
LL begin = Map[i][j] - i - j + 2;
if(begin<=Map[1][1]) ans = min(ans, solve(begin));
//否则实际起点值比理论起点值还小,这个起点值是不可用的
}
}
cout << ans << endl;
}
return 0;
}