一、题意

给一个n行m列的整数矩阵(n<=10, m<=100),从第一列的任何一个位置出发,每次可以往→、↗、↘走一格,最终到达最后一列。矩阵时环形的,即第一行的上一行是最后一行,最后一行的下一行是第一行。现在要你找一条路线使得经过的数字之和最小。
打印出路径和最小和。
若有多解则输出字典序最小的。

二、解析

这是一道多阶段决策问题,一般这种问题都可以通过递推法的动态规划解决。

用dp[i][j]表示从a[i][j]开始的路径到达最后一列的最小和,的转移方程可以很容易得到:即dp[i][j]=a[i][j]+min(dp[→],dp[↗],dp[↘])。

这道题还要输出路径。这里主要复习一下通过递归打印路径的方法。这种方法的好处在于不需要维护新的存放路径信息的数组,直接利用dp[i][j]的关系来打印路径;当然会慢一点,但是不会影响时间复杂度,因此是我比较喜欢的方法(不用动脑就是好)。

三、代码

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
const int maxn = 10 + 5, maxm = 100 + 5, INF = 1 << 30;
int n, m, a[maxn][maxm], dp[maxn][maxm];

void print_path(int i, int j) {
    if(j == m) return;
    if(j) cout << " ";
    cout << i + 1;

    vector<int> next = {(i - 1 + n) % n, i, (i + 1) % n};
    sort(next.begin(), next.end());
    for(auto ni : next) if(dp[ni][j + 1] + a[i][j] == dp[i][j]) {
        print_path(ni, j + 1);
        break;
    }
}

int main() {
    while(cin >> n >> m) {
        for(int i = 0; i < n; i ++) for(int j = 0; j < m; j ++) cin >> a[i][j];
        for(int i = 0; i < n; i ++) dp[i][m] = 0;
        for(int j = m - 1; j >= 0; j --) {
            for(int i = 0; i < n; i ++) {
                dp[i][j] = a[i][j] + min(dp[i][j + 1], min(dp[(i - 1 + n) % n][j + 1], dp[(i + 1) % n][j + 1]));
            }
        }
        int ans = INF, ans_idx = 0;
        for(int i = 0; i < n; i ++) if(dp[i][0] < ans) ans = dp[i][0], ans_idx = i;
        print_path(ans_idx, 0);
        cout << "\n" << ans << endl;
    }
}

四、归纳

  • 多阶段决策问题,可以使用动态规划来解决
  • 通过递归来print_ans是一种比较方便的方法,因为它不需要额外维护存储路径信息的数组。