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;
}