NEFU-2023-算法设计与分析实验二动态规划算法设计

作者的话

大底包含了算法硬性规定的作业代码,并非最优解,仅供参考并会持续更新。勿要无脑copy,对自己负责。如果代码有误或者优化建议,直接相应博客下方评论或者qq找我如果对代码有理解不了的或者疑惑可以询问我,但是请确保你已经自己思考过或者查过搜索引擎(如果我原模原样搜到了资料的话我会锤你的hh)。一些语法和库的资料查询网站受个人风格影响,部分题目解题方式可能没按照教学要求,如有必要请自己按教学要求做一遍。如果可以的话,麻烦借鉴完以后给我博客来个三连啥的,这可能对我以后找工作或者其他博客的推广什么的有些帮助,感谢

如要系统学习可看我的另一篇博客算法小课堂(四)动态规划

题目一 数字三角问题

问题描述:给定一个由n行数字组成的数字三角形,如下图所示

          7

       3   8

     8   1   0

  2    7   4   4

4    5   2   6   5

试设计一个算法,计算出从三角形的顶至底的一条路径,使该路径经过的数字总和最大。

如上图最大值为30=7+3+8+7+5

正序解法C++

#include <iostream>

using namespace std;

const int N = 510, INF = 1e9;
int f[N][N]; // f[i][j]表示到第i行第j列的最大路径和
int a[N][N]; // 存放输入的矩阵

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            scanf("%d", &a[i][j]);

    // 初始化
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            f[i][j] = -INF;
    f[1][1] = a[1][1];

    // 动态转移
    for (int i = 2; i <= n; i++)
        for (int j = 1; j <= i; j++)
            f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);

    // 求解答案
    int max0 = -INF;
    for (int j = 1; j <= n; j++)
        max0 = max(max0, f[n][j]);

    cout << max0 << endl;
    return 0;
}

正序解法JAVA

import java.util.*;

public class Main {
    static final int N = 510, INF = 1_000_000_000;
    static int[][] f = new int[N][N]; // f[i][j]表示到第i行第j列的最大路径和
    static int[][] a = new int[N][N]; // 存放输入的矩阵

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= i; j++)
                a[i][j] = sc.nextInt();

        // 初始化
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= i; j++)
                f[i][j] = -INF;
        f[1][1] = a[1][1];

        // 动态转移
        for (int i = 2; i <= n; i++)
            for (int j = 1; j <= i; j++)
                f[i][j] = Math.max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);

        // 求解答案
        int max0 = -INF;
        for (int j = 1; j <= n; j++)
            max0 = Math.max(max0, f[n][j]);

        System.out.println(max0);
    }
}

倒序解法

f(i,j)含义: 从最底层出发到第 i 行第 j 个数的最大路径和

每个点有两种选择: 向左上方走 和 向右上方走

对应的子问题为: ​​ f(i+1,j) ​​ 和 f(i+1,j+1)

倒序情况下不需要考虑边界条件

结果: f(1,1)

#include <iostream>

using namespace std;

const int N = 510;
int f[N][N];

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

    // 读入三角形中的数值
    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= i; j++)
            cin >> f[i][j];

    // 倒序 DP,从最后一行开始向上推
    for(int i = n; i >= 1; i--)
        for(int j = 1; j <= i; j++)
            f[i][j] += max(f[i+1][j], f[i+1][j+1]);

    cout << f[1][1] << endl;    // 输出最终的结果
    return 0;
}

二维优化为一维

#include <iostream>
using namespace std;
const int N = 510, INF = 1e9;

int f[N]; // f[j]表示到达当前行第j列的最大路径和
int a[N][N]; // 存放输入的矩阵

int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            scanf("%d", &a[i][j]);

    // 初始化
    for (int j = 1; j <= n; j++)
        f[j] = -INF;
    f[1] = a[1][1];

    // 动态转移
    for (int i = 2; i <= n; i++)
        for (int j = i; j >= 1; j--)
            f[j] = max(f[j], f[j - 1]) + a[i][j];

    // 求解答案
    int max0 = -INF;
    for (int j = 1; j <= n; j++)
        max0 = max(max0, f[j]);

    cout << max0 << endl;
    return 0;
}

记忆化搜索

#include <iostream>
using namespace std;

const int N = 510;

int n;
int d[N][N];
int f[N][N];

int dfs(int i, int j) {
    if (i == n) return d[i][j]; // 最底层

    if (f[i][j] != -1) return f[i][j]; // 记忆化

    int l = dfs(i + 1, j);
    int r = dfs(i + 1, j + 1);

    return f[i][j] = max(l, r) + d[i][j];
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            cin >> d[i][j];
            f[i][j] = -1; // 初始化
        }
    }

    cout << dfs(1, 1) << endl;

    return 0;
}

回溯法

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

const int N = 510, INF = 1e9;
int f[N][N]; // f[i][j]表示到第i行第j列的最大路径和
int a[N][N]; // 存放输入的矩阵
int path[N];//用来记录路径

void dfs(int x, int y,int n) {
    path[n] = a[x][y];
    if (x == 1) {
        return;
    }
    if (f[x - 1][y] > f[x - 1][y - 1]) {
        dfs(x - 1, y,n-1);
    }
    else {
        dfs(x - 1, y - 1,n-1);
    }
}

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            scanf("%d", &a[i][j]);

    // 初始化1
    /*for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            f[i][j] = -INF;*/
    // 初始化2
    memset(f,-INF,sizeof(f));

    f[1][1] = a[1][1];

    // 动态转移
    for (int i = 2; i <= n; i++){
        for (int j = 1; j <= i; j++)
            f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);


    }
    // 求解答案
    int max0 = -INF;
    int x = n,y=1;
    for (int j = 1; j <= n; j++)
        if (f[n][j] > f[n][y])
            y = j;
    cout << f[n][y] << endl;
    dfs(x,y,n);
    for(int i = 1;i <= n;i++)
        cout<<path[i]<<" ";
    return 0;
}

题目二最长公共子序列问题

2、最长公共子序列问题

问题描述:给定两个序列X={x1,x2,...,xm}和Y={y1,y2,...,yn},找出X和Y的最长公共子序列。

输入:

第1行:两个子序列的长度,m n

第2行:第1个子序列的各个元素(序列下标从1开始)

第3行:第2个子序列的各个元素(序列下标从1开始)

输出:

最长公共子序列

实例:

输入:

第1行:

4 5            //m和n的值

第2行

abad        //输入4个字符,下标从1开始

第3行

baade      //输入5个字符,下标从1开始

输出:

aad

实验书模板

朴素解法

#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N]; // f[i][j] 表示 a 的前 i 个字符和 b 的前 j 个字符的最长公共子序列长度
int main() {
  cin >> n >> m >> a + 1 >> b + 1; // 输入 n、m 和字符串 a、b,注意要从下标 1 开始读入
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      if (a[i] == b[j]) { // 如果当前字符相同,则最长公共子序列长度加一
        f[i][j] = f[i - 1][j - 1] + 1;
      } else { // 如果当前字符不同,则最长公共子序列长度为 a 的前 i-1 个字符和 b 的前 j 个字符的最长公共子序列长度,或者是 a 的前 i 个字符和 b 的前 j-1 个字符的最长公共子序列长度中的较大值
        f[i][j] = max(f[i - 1][j], f[i][j - 1]);
      }
    }
  }
  // 回溯输出最长公共子序列
  int len = f[n][m];
  char ans[len+1]; // 存储最长公共子序列
  ans[len] = '\0'; // 字符串末尾要加上'\0'
  int i = n, j = m;
  while (len > 0) {
    if (a[i] == b[j]) { // 如果a[i]和b[j]相同,则说明该字符在最长公共子序列中
      ans[len-1] = a[i];
      len--;
      i--;
      j--;
    } else { // 如果a[i]和b[j]不同,则说明f[i][j]是由f[i-1][j]和f[i][j-1]中较大的那个转移过来的
      if (f[i-1][j] > f[i][j-1]) {
        i--;
      } else {
        j--;
      }
    }
  }
  cout << ans << '\n'; // 输出最长公共子序列
  return 0;
}

记忆化搜索

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

const int N = 2000; // 总预算
const int K = 6; // 商品数量
int p[K+1]; // 商品价格
int w[K+1]; // 商品重要度

int dp[K+1][N+1];

// 记忆化搜索函数
int dfs(int i, int j) {
    // 如果已经计算过当前状态,则直接返回结果
    if (dp[i][j] != -1) return dp[i][j];
    // 如果已经处理完所有商品或者总预算为0,则返回0
    if (i == 0 || j == 0) return dp[i][j] = 0;
    // 不选第i件商品
    int res = dfs(i-1, j);
    // 选第i件商品
    if (j >= p[i]) {
        res = max(res, dfs(i-1, j-p[i]) + p[i]*w[i]);
    }
    // 将结果存储在数组中以备后续使用
    return dp[i][j] = res;
}

int main() {

    // 初始化dp数组为-1,表示当前状态还未计算过
    memset(dp, -1, sizeof(dp));

    // 输入商品价格和重要度
    for (int i = 1; i <= K; i++) {
        cin >> p[i] >> w[i];
    }

    // 输出最大价值
    cout << dfs(K, N) << endl;

    return 0;
}

题目三日常购物

3、日常购物

问题描述:小明今天很开心,因为在家买的新房子即将拿到钥匙。新房里面有一间他自己专用的、非常宽敞的房间。让他更高兴的是,他的母亲昨天对他说:“你的房间需要购买什么物品?怎么布置,你说了算,只要他们的价格总和不超过N元钱”。小明今天早上开始预算,但他想买太多的东西,肯定会超过母亲的N元限额。因此,他把对每件物品的渴望程度,分为5等级:用整数1->5表示,第5等表示最想要。他还从互联网上找到了每件商品(所有整数)的价格。他希望在不超过N元(可能等于N元)的情况下,将每件商品的价格与效益度的乘积的总和最大化.

设第j件物品的价格为p[j],重要度为w[j],其选中的k件商品,编号依次为j1,j2,……,jk,则所求的总和为:

p[j1]×w[j1]+p[j2]×w[j2]+ …+p[jk]×w[jk]。

请帮小明设计一个符合要求的购物清单。

其中N=2000,K=6

p[1]=200 w[1]=2

p[2]=300 w[2]=2

p[3]=600 w[3]=1

p[4]=400 w[4]=3

p[5]=1000 w[5]=4

p[6]=800 w[6]=5

朴素解法

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

const int N = 2000; // 总预算
const int K = 6; // 商品数量
int p[K+1]; // 商品价格
int w[K+1]; // 商品重要度

int dp[K+1][N+1];

int main() {

    for (int i = 1; i <= K; i++) {
        cin >> p[i] >> w[i];
    }

    for (int i = 1; i <= K; i++) {
        for (int j = 1; j <= N; j++) {
            // 不选第i件商品
            dp[i][j] = dp[i-1][j];
            // 选第i件商品
            if (j >= p[i]) {
                dp[i][j] = max(dp[i][j], dp[i-1][j-p[i]] + p[i]*w[i]);
            }
        }
    }

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

    return 0;
}

记忆化搜索

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

const int N = 2000; // 总预算
const int K = 6; // 商品数量
int p[K+1]; // 商品价格
int w[K+1]; // 商品重要度

int dp[K+1][N+1];

// 记忆化搜索函数
int dfs(int i, int j) {
    // 如果已经计算过当前状态,则直接返回结果
    if (dp[i][j] != -1) return dp[i][j];
    // 如果已经处理完所有商品或者总预算为0,则返回0
    if (i == 0 || j == 0) return dp[i][j] = 0;
    // 不选第i件商品
    int res = dfs(i-1, j);
    // 选第i件商品
    if (j >= p[i]) {
        res = max(res, dfs(i-1, j-p[i]) + p[i]*w[i]);
    }
    // 将结果存储在数组中以备后续使用
    return dp[i][j] = res;
}

int main() {

    // 初始化dp数组为-1,表示当前状态还未计算过
    memset(dp, -1, sizeof(dp));

    // 输入商品价格和重要度
    for (int i = 1; i <= K; i++) {
        cin >> p[i] >> w[i];
    }

    // 输出最大价值
    cout << dfs(K, N) << endl;

    return 0;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烟雨平生9527

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值