算法第三章作业
从路径约束到状态转移:动态规划求解“最低通行费”
一、问题与思路
“最低通行费”要求商人从 N×N 网格的左上角出发,到达右下角,并在经过每个格子时支付相应费用。商人必须在 2N−1 个单位时间内通过网格,且只能上下左右移动。
解题的第一步,是弄清楚时间限制对路线有什么影响。从左上角到右下角,至少需要向右走 N−1 步、向下走 N−1 步,共移动 2N−2步。加上出发时所在的格子,最短路线一共经过 2N−1 个格子,正好符合题目规定的时间。
因此,满足时限的路线只能由向右和向下的移动组成。任何向上或向左的绕行都会增加经过的格子数,无法按时完成。这样,原问题就转化成:在只能向右、向下移动的条件下,寻找从左上角到右下角的最小费用路线。
二、最优子结构与递推方程
设 cost[i][j] 表示第 i 行、第 j 列格子的通行费,定义:
dp[i][j] 表示从左上角到达第 i 行、第 j 列时的最小总费用,包含起点和当前格子的费用。
到达格子 (i,j)(i,j) 时,最后一步只能从上方 (i−1,j)(i−1,j) 或左方 (i,j−1)(i,j−1) 走来。无论从哪边到达,都需要支付当前格子的费用。因此,对于非边界位置,有递推方程:
dp[i][j]=min(dp[i−1][j], dp[i][j−1])+cost[i][j]
这个递推关系体现了问题的最优子结构:如果到达 (i,j)(i,j) 的最优路线是从上方来的,那么它到达上方格子的那一段也必须是到达该格子的最优路线。否则,可以用一条费用更低的路线替换前半段,得到费用更低的完整路线,与原路线最优矛盾。从左方到达的情况也相同。
边界条件
起点没有前驱,因此:
dp[1][1]=cost[1][1]dp[1][1]=cost[1][1]
第一行只能从左边到达:
dp[1][j]=dp[1][j−1]+cost[1][j],2≤j≤N dp[1][j]=dp[1][j−1]+cost[1][j],2≤j≤N
第一列只能从上方到达:
dp[i][1]=dp[i−1][1]+cost[i][1],2≤i≤N dp[i][1]=dp[i−1][1]+cost[i][1],2≤i≤N
三、填表方法与答案位置
dp 表是一个 N×N 的二维表,填表范围为 1≤i,j≤N 1≤i,j≤N。
填表时从左上角开始,按行从左向右、从上向下计算。计算 dp[i][j] 时,依赖的上方状态 dp[i-1][j] 和左方状态 dp[i][j-1] 已经填好,因此可以直接使用递推方程。
原问题要求的是从左上角到右下角的最小费用,所以最终答案是:
dp[N][N]
四、复杂度分析
网格中有 N2 个格子,每个状态只需进行常数次比较和加法,因此:
时间复杂度: O(N2)
空间复杂度: O(N2)
五、C++ 参考实现
cpp
include
include
include using namespace std;
int main() {
int n;
cin >> n;
vector<vector
vector<vector
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
cin >> cost[i][j];
}
}
dp[1][1] = cost[1][1];
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (i == 1 && j == 1) {
continue;
}
if (i == 1) {
dp[i][j] = dp[i][j - 1] + cost[i][j];
} else if (j == 1) {
dp[i][j] = dp[i - 1][j] + cost[i][j];
} else {
dp[i][j] =
min(dp[i - 1][j], dp[i][j - 1]) + cost[i][j];
}
}
}
cout << dp[n][n] << '\n';
return 0;
}
以题目样例为例,最优路线的费用为:
1+2+5+7+9+12+19+21+33=109 1+2+5+7+9+12+19+21+33=109
程序最终得到 dp[5][5] = 109,与样例结果一致。
六、对动态规划的理解与体会
通过这道题,我对动态规划的求解过程有了更具体的认识。设计动态规划时,首先要分析题目约束,判断哪些路径或方案是可行的。本题虽然允许上下左右移动,但时间限制实际上排除了所有绕路,使得路线只能向右或向下。
在此基础上,定义清楚状态的含义非常重要。dp[i][j] 记录的是到达某个格子的最小累计费用,而不只是该格子本身的费用。明确状态后,再从“到达当前位置的最后一步可能来自哪里”出发,就能得到递推方程。
我也体会到,边界条件和填表顺序是递推关系能够正确运行的基础。动态规划通过保存已经求出的子问题结果,避免重复计算,并逐步得到整个问题的最优解。今后遇到类似问题时,我会先分析限制条件,再依次确定状态、转移方程、边界条件和填表顺序。

浙公网安备 33010602011771号