第三章算法作业

第三章算法作业

1.1 最优子结构与递归方程式

问题分析:
商人从网格左上角进入,右下角离开,且必须在 2N- 个单位时间内穿越出去。由于每经过一个格子消耗 1 个单位时间,从左上角到右下角至少需要经过 2N-1 个格子,因此商人只能向右或向下移动,不能回头。

状态定义:
设 dp[i][j] 表示从左上角 (0,0) 走到格子 (i,j) 所需的最小费用。

递归方程式:

if (i == 0 && j == 0)
    dp[i][j] = p[i][j];
else if (i == 0)
    dp[i][j] = dp[i][j-1] + p[i][j];
else if (j == 0)
    dp[i][j] = dp[i-1][j] + p[i][j];
else
    dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + p[i][j];

边界条件:

  • 起点:dp[0][0] = p[0][0]。
  • 第一行:只能从左边格子过来,因此 dp[0][j] = dp[0][j-1] + p[0][j]。
  • 第一列:只能从上边格子过来,因此 dp[i][0] = dp[i-1][0] + p[i][0]。

最优子结构:
到达 (i,j) 的最小费用路径中,到达 (i-1,j) 或 (i,j-1) 的子路径也必须是最小费用路径。否则,可以用更优的子路径替换,从而得到更优的整体解,这与最优解矛盾。因此该问题具有最优子结构性质。


1.2 填表法分析

项目 说明
表的维度 二维表 dp[N][N]
填表范围 i ∈ [0, N-1],j ∈ [0, N-1]
填表顺序 按行从上到下、每行从左到右依次填表
依赖关系 计算 dp[i][j] 时需要 dp[i-1][j] 和 dp[i][j-1],按上述顺序可保证子问题已求解
原问题最优值 dp[N-1][N-1],即从左上角到右下角的最小费用

1.3 时间与空间复杂度分析

时间复杂度:
程序使用两层循环遍历整 网格,每个格子只计算一次,每次计算只进行常数次比较和加法操作,因此时间复杂度为:O(N^2)

空间复杂度:
程序使用了两个二维数组 p[MAXN][MAXN] 和 dp[MAXN][MAXN],每个数组大小为,因此空间复杂度为:O(N^2)

如果进一步优化,可以只保留一行或一列的 DP 值,将空间复杂度降低到 O(N)。


对动态规划算法的理解和体会

通过这次实验,我对动态规划算法有了更具体的认识。动态规划并不是简单地“套公式”,而是要先分析问题的最优子结构,再设计合适的状态表示,最后根据状态之间的依赖关系写出递推式。

在这道“最低通行费”题目中,最关键的一步是确定状态 dp[i][j] 表示从起点走到 (i,j) 的最小费用。状态确定后,转移方程就比较自然:商人只能从上方或左方进入当前格子,因此取两者中的较小值,再加上当前格子的费用即可。

同时,边界条件也非常重要。第一行和第一列没有两个方向可以转移,必须单独处理。填表顺序也需要保证子问题先被计算,否则会出现使用未初始化值的情况。

与暴力搜索相比,动态规划通过保存子问题的解,避免了重复计算,显著提高了效率。这种“以空间换时间”的思想让我认识到,算法设计不仅要考虑结果是否正确,还要考虑如何减少不必要的计算。动态规划对问题建模能力要求较高,但只要状态定义清晰,递推关系往往就能比较自然地得到。
代码实现如下:

#include <iostream>
#include algorithm>
using namespace std;

const int MAXN = 105;
int p[MAXN][MAXN];
int dp[MAXN][MAXN];

int main() {
    int N;
    cin >> N;
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            cin >> p[i][j];

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            if (i == 0 && j == 0) {
                dp[i][j] = p[i][j];           // 起点
            } else if (i == 0) {
                dp[i][j] = dp[i][j-1] + p[i][j];  // 第一行,只能从左边来
            } else if (j == 0) {
                dp[i][j] = dp[i-1][j] + p[i][j];  // 第一列,只能从上边来
            } else {
                dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + p[i][j];
            }
        }
    }

    cout << dp[N-1][N-1] << endl;
    return 0;
}

posted @ 2026-11-02 18:41  烧烧洋芋  阅读(4)  评论(0)    收藏  举报