第三章作业

第三章作业:7-4 最低通行费

一、动态规划实践报告
1.1 最优子结构分析、状态定义与递归方程式
(1)题目给定一个 N*N 的正方形网格,每个方格都有对应的通行费用,要求商人在 (2N-1) 个单位时间内从左上角 (1, 1) 穿越到右下角 (N, N) 且总费用最少。虽然题目表面允许上下左右四个方向移动,但从左上角 (1, 1) 到右下角 (N, N) 的最短路径恰好需要经过 (N-1) + (N-1) + 1 = 2N-1 个方格。这一时间限制实际上将可行移动方向锁死为只能向右或向下移动,因为一旦向上或向左走回头路,所经过的方格数必然超过 2N-1。
(2)设二维数组 dp[i][j] 表示从起点 (1, 1) 出发,走到第 i 行、第 j 列方格 (i, j) 所需支付的最小累计通行费用(包含方格 (i, j) 本身的费用),并设 cost[i][j] 为网格中第 i 行第 j 列方格自身的通行费用。
(3)由于商人只能向右或向下移动,到达方格 (i, j) 的最后一步只可能来自正上方方格 (i-1, j) 或正左方方格 (i, j-1)。如果到达 (i, j) 的路径是全局最优解,那么它到达前置位置 (i-1, j) 或 (i, j-1) 的那段子路径也必然是对应点的最优路径;否则,用费用更低的前置路径替换,就能得到更优的总费用,这与最优解假设矛盾。因此,该问题具有最优子结构性质和无后效性,可以通过动态规划求解。
(4)当 i > 1 且 j > 1 时,到达当前位置只能从上方或左方进入,只需选择这两种方式中费用较小的一个,再加上当前方格的费用即可:
dp[i][j] = cost[i][j] + min(dp[i-1][j], dp[i][j-1])
(5)边界条件:为了让状态转移能够正常进行,需要先确定起点及第一行、第一列的初始状态:
起点位置(i = 1, j = 1):商人从左上角出发,初始费用就是起点方格本身的费用,即 dp[1][1] = cost[1][1]。
第一行(i = 1, j > 1):只能从左方一路向右走过来,所以 dp[1][j] = dp[1][j-1] + cost[1][j]。
第一列(i > 1, j = 1):只能从上方一路向下走过来,所以 dp[i][1] = dp[i-1][1] + cost[i][1]。

1.2 填表法设计
(1)建立一个大小为 (N+1)(N+1) 的二维数组 dp[N+1][N+1],数组下标从 1 开始,以直接对应网格的行列坐标。
(2)填表范围为整个 N
N 网格,外层循环行下标 i 从 1 遍历到 N,内层循环列下标 j 从 1 遍历到 N,覆盖全部 N^2 个位置。
(3)采用按行优先的顺序,从上到下、每行从左到右(即 i 递增、j 递增)依次填表。因为在计算 dp[i][j] 时,需要依赖正上方元素 dp[i-1][j] 和正左方元素 dp[i][j-1],按照从上到下、从左到右的顺序填表,可以保证每次计算当前状态时,所需的上方和左方状态都已经计算完毕。
(4)当整个二维表格填充完成后,存储在表格右下角的元素 dp[N][N] 即为原问题的最优解,即从左上角到右下角的最低通行费。
(5)C++ 代码实现

include

include

using namespace std;

int main() {
int N;
cin >> N;

int cost[105][105];
int dp[105][105] = {0};

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 j = 2; j <= N; j++) {
    dp[1][j] = dp[1][j - 1] + cost[1][j];
}

for (int i = 2; i <= N; i++) {
    dp[i][1] = dp[i - 1][1] + cost[i][1];
}

for (int i = 2; i <= N; i++) {
    for (int j = 2; j <= N; j++) {
        dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + cost[i][j];
    }
}

cout << dp[N][N] << endl;

return 0;

}

1.3 算法复杂度分析
(1)时间复杂度:O(N^2)。
程序通过两层嵌套的 for 循环遍历整个 NN 网格的 N^2 个状态,每个位置只需要进行一次最小值比较和一次加法运算,状态转移所花费的时间为 O(1)。对比暴力搜索,动态规划将时间复杂度降低到了多项式级别。
(2)空间复杂度:O(N^2)。
代码中使用了二维数组 cost 和 dp 分别保存网格费用和动态规划计算结果,大小均为 N
N 级别。

二、对动态规划算法的理解和体会
通过完成“最低通行费”这道网格路径规划题,我对动态规划算法的设计步骤与核心思想有了更加具体的认识:
第一,审题建模与隐含条件挖掘是解题的前提。解决动态规划问题不能盲目套用公式,首先要理解题意并找出状态之间的联系。本题表面上允许上下左右四向移动,但“2N-1 个单位时间”的约束直接排除了走回头路的可能,将移动方向限制为只能向右或向下,使得状态转移方程从复杂的四路搜索瞬间简化为上方和左方两路取最小。
第二,动态规划的核心是以空间换时间,利用最优子结构与无后效性消除重复计算。暴力搜索之所以慢,是因为到达某个中间格子的最优路径会在后续无数条不同的大路径中被反复重算。动态规划通过引入 dp 数组把每个子问题的计算结果记录下来,后续需要时直接查表读取。同时,无论商人之前怎么走到当前格子 (i, j),一旦当前状态确定,之前的路径对后续向右或向下的选择都不产生影响,这种无后效性保证了递推的正确性。
第三,填表顺序与边界细节决定了代码实现的成败。当前状态的计算依赖于前面已经算好的结果,因此必须严格按照从上到下、从左到右的顺序填表。此外,在编写代码时,第一行和第一列只有单一的转移来源,提前将起点、第一行和第一列作为边界条件单独初始化,不仅让逻辑更加清晰,也能安全地避免数组越界问题。

posted @ 2026-11-01 19:48  M1LK  阅读(5)  评论(0)    收藏  举报