动态规划 | 数字三角形模型 | 类似题型一网打尽

文章目录

    • 数字三角形
      • 题目描述
      • 问题分析
      • 程序代码
      • 复杂度分析
    • 摘花生
      • 题目描述
      • 问题分析
      • 程序代码
      • 复杂度分析
    • 最低通行费
      • 题目描述
      • 问题分析
      • 程序代码
      • 复杂度分析
    • 方格取数
      • 题目描述
      • 问题分析
      • 程序代码
      • 复杂度分析

数字三角形

题目描述

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5

问题分析

自顶向下,末状态不唯一。由于结果与路径顺序无关,因此我们可以反过来,自底向上,这样末状态唯一,为数字三角形的顶点(1, 1)

动态规划 | 数字三角形模型 | 类似题型一网打尽_第1张图片

状态表示dp[i][j] 表示从底向上,走到(i, j)的所有路径的最大值。

状态计算(i, j)可以由(i + 1, j)(i + 1, j + 1)两个状态转移过来,即dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + w[i][j]

程序代码

#include 
#include 
#include 
using namespace std;

int main()
{
    int n;
    cin >> n;
    vector<vector<int>> w(n + 1, vector<int>(n + 1, 0));
    vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= i; j++) {
            cin >> w[i][j];
        }
    }
    // 初始化最底层
    for(int i = 1; i <= n; i++)  dp[n][i] = w[n][i];
    // 自底向上
    for(int i = n - 1; i >= 1; i--) {
        for(int j = 1; j <= i; j++) {
            dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + w[i][j];
        }
    }
    cout << dp[1][1] << endl;
    return 0;
}

复杂度分析

时间复杂度为 O ( n 2 ) O(n^2) O(n2)

摘花生

题目描述

Hello Kitty想摘点花生送给她喜欢的米老鼠。

她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。

地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。

Hello Kitty只能向东或向南走,不能向西或向北走。

问Hello Kitty最多能够摘到多少颗花生。

动态规划 | 数字三角形模型 | 类似题型一网打尽_第2张图片

问题分析

状态表示dp[i][j]表示所有从起点(1, 1)走到(i, j)的路线中,摘取花生的最大值

状态计算

  • 自然语言:(i, j)这个状态要么从上面那个点(i-1, j)转移过来,要么从左边那个点(i, j-1)转移过来,二者取一个最大值
  • 数学语言:dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + w[i][j]

程序代码

#include 
#include 
#include 
using namespace std;

int main()
{
    int t;
    cin >> t;
    // t组数据
    while( t-- ) {
        int r, c;
        cin >> r >> c;
        vector<vector<int>> w(r + 1, vector<int>(c + 1, 0));
        for(int i = 1; i <= r; i++) {
            for(int j = 1; j <= c; j++) {
                cin >> w[i][j];
            }
        }
        vector<vector<int>> dp(r + 1, vector<int>(c + 1, 0));
        for(int i = 1; i <= r; i++) {
            for(int j = 1; j <= c; j++) {
                dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + w[i][j];
            }
        }
        cout << dp[r][c] << endl;
    }
    return 0;
}

复杂度分析

时间复杂度为 O ( N 2 ) O(N^2) O(N2) 级别

最低通行费

题目描述

一个商人穿过一个 N×N 的正方形的网格,去参加一个非常重要的商务活动。

他要从网格的左上角进,右下角出。

每穿越中间 1 个小方格,都要花费 1 个单位时间。

商人必须在 (2N−1) 个单位时间穿越出去。

而在经过中间的每个小方格时,都需要缴纳一定的费用。

这个商人期望在规定时间内用最少费用穿越出去。

请问至少需要多少费用?

注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。

问题分析

根据题意分析,为了获取最小费用,不能走回头路

状态表示dp[i][j]表示所有从起点(1, 1)走到(i, j)的路线中,缴纳的最小费用

状态计算

  • 自然语言:(i, j)这个状态要么从上面那个方格(i-1, j)转移过来,要么从左边那个方格(i, j-1)转移过来,二者取一个最小值
  • 数学语言:dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + w[i][j]

程序代码

#include 
#include 
#include 
using namespace std;

int main()
{
    int n;
    cin >> n;
    vector<vector<int>> w(n + 1, vector<int>(n + 1, 0));
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            cin >> w[i][j];
        }
    }
    vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
    // 初始化边界
    for(int i = 1; i <= n; i++) {
        dp[i][1] = dp[i-1][1] + w[i][1];
        dp[1][i] = dp[1][i-1] + w[1][i];
    }
    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]) + w[i][j];
        }
    }
    cout << dp[n][n] << endl;
    return 0;
}

复杂度分析

时间复杂度为 O ( N 2 ) O(N^2) O(N2)

方格取数

题目描述

设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:

动态规划 | 数字三角形模型 | 类似题型一网打尽_第3张图片

某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点。

在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。

此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。

问题分析

这题与摘花生问题的不同之处在于,要走两次,且同一个格子的数只能被取一次,取完后归 0。

同一个格子的数只能被取一次的处理方式

  • 只有当i1 + j1 == i2 + j2时,两条路径的格子才可能重合
  • 因此,当i1 + j1 == i2 + j2时,分别处理格子重合和不重合的情况

状态表示:

  • dp[k][i1][i2]:从起点分别走到(i1, k - i1)(i2, k - i2)的路径中,取得数字和的最大值。
  • 这里的 k 表示两条路线当前走到格子的横纵坐标之和。

状态计算:

  • 若初始状态为dp[k][i1][i2],两条路线可能的状态转移情况:
    • 下下:dp[k+1][i1+1][i2+1]
    • 下右:dp[k+1][i1+1][i2]
    • 右右:dp[k+1][i1][i2]
    • 右下:dp[k+1][i1][i2+1]
  • 目标状态可能的情况:
    • 目标状态重合:+ w[i1][j1]
    • 目标状态不重合:+ w[i1][j1] + w[i2][j2]

程序代码

#include 
#include 
using namespace std;

int main()
{
    int n;
    cin >> n;
    vector<vector<int>> w(n + 1, vector<int>(n + 1, 0));
    vector<vector<vector<int>>> dp(n * 2 + 1, vector<vector<int>>(n + 1, vector<int>(n + 1, 0)));
    int a, b, c;
    // 当a,b,c均为0时,停止输入
    while( cin >> a >> b >> c, a || b || c ) {
        w[a][b] = c;
    }
    for(int k = 2; k <= n*2; k++) {
        for(int i1 = 1; i1 <= n; i1++) {
            for(int i2 = 1; i2 <= n; i2++) {
                int j1 = k - i1;
                int j2 = k - i2;
                // 判断是否越界
                if(j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n) {
                    // 格子重合
                    int t = w[i1][j1];
                    // 格子不重合
                    if(i1 != i2)  t += w[i2][j2];
                    int &x = dp[k][i1][i2];  // 引用,简化代码
                    x = max(x, dp[k-1][i1-1][i2-1] + t);  // 下下
                    x = max(x, dp[k-1][i1-1][i2] + t);  // 下右
                    x = max(x, dp[k-1][i1][i2-1] + t);  // 右下
                    x = max(x, dp[k-1][i1][i2] + t);  // 右右
                }
            }
        }
    }
    cout << dp[n*2][n][n] << endl;
    return 0;
}

复杂度分析

时间复杂度为 O ( N 3 ) O(N^3) O(N3)

你可能感兴趣的:(手撕算法,动态规划,算法)