作者的话
大底包含了算法硬性规定的作业代码,并非最优解,仅供参考并会持续更新。勿要无脑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;*/// 初始化2memset(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,则返回0if (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,则返回0if (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;
}