自己学校的作业,删了觉得太可惜了,不如发出来
动态规划
动态规划就是一个优化版的暴力法, 优化了遍历的顺序和数量, 使得将一个问题降低到一个时间可观的复杂度以内
最长公共子序列
给定两个长度分别为 NNN 和 MMM 的字符串 AAA 和 BBB,求既是 AAA 的子序列又是 BBB 的子序列的字符串长度最长是多少, 并输出该字串。
输入格式
第一行包含两个整数 NNN 和 MMM。
第二行包含一个长度为 NNN 的字符串,表示字符串 AAA。
第三行包含一个长度为 MMM 的字符串,表示字符串 BBB。
字符串均由小写字母构成。
输出格式
第一行输出一个整数,表示最大长度。
第二行输出一个字符串,表示该字串
数据范围
1≤N,M≤10001≤N,M≤10001≤N,M≤1000
输入样例:
4 5
acbd
abedc
输出样例:
3
abd
C++代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N], c[N];
int dp[N][N];
int main() {
cin >> n >> m; // 读取两个序列的长度
scanf("%s", a + 1); // 读取a的值
scanf("%s", b + 1); // 读取b的值
for (int i = 1; i <= n; i++) { // dp求解
for (int j = 1; j <= m; j++) {
if (a[i] == b[j]) {
dp[i][j] = max(dp[i - 1][j - 1] + 1, dp[i][j]);
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
cout << "最长的子序列的长度: " << dp[n][m] << endl;
int len = 0; // 子序列的长度
for (int i = n, j = m; i >= 1 && j >= 1;) {
if (a[i] == b[j]){
c[len ++] = a[i];
i --, j --;
} else {
if (dp[i - 1][j] > dp[i][j - 1])i --;
else j --;
}
}
reverse(c, c + len);
cout << "子序列为: " << c << endl;
return 0;
}
运行截图
算法分析
集合表示:f[i][j]f[i][j]f[i][j] 表示 aaa 的前 iii 个字母,和 bbb 的前 jjj 个字母的最长公共子序列长度
集合划分:以 a[i],b[j]a[i],b[j]a[i],b[j] 是否包含在子序列当中为依据,因此可以分成四类:
-
a[i]a[i]a[i] 不在,b[j]b[j]b[j] 不在
f[i][j]=f[i−1][j−1]f[i][j] = f[i - 1][j - 1]f[i][j]=f[i−1][j−1]
-
a[i]a[i]a[i] 在, b[j]b[j]b[j] 不在
f[i][j]=max(f[i][j],f[i][j−1])f[i][j] = max(f[i][j],f[i][j - 1])f[i][j]=max(f[i][j],f[i][j−1])
-
a[i]a[i]a[i] 不在, b[j]b[j]b[j] 在
f[i][j]=max(f[i][j],f[i−1][j])f[i][j] = max(f[i][j],f[i - 1][j])f[i][j]=max(f[i][j],f[i−1][j])
-
a[i]a[i]a[i] 在, b[j]b[j]b[j] 在
f[i][j]=max(f[i][j],f[i][j]+1)f[i][j] = max(f[i][j],f[i][j] + 1)f[i][j]=max(f[i][j],f[i][j]+1)
复杂度分析:
aaa 串和 bbb 串均会被遍历一次, 所以时间复杂度为 O(N∗M)=O(n2)O(N*M)=O(n^{2})O(N∗M)=O(n2) , NNN 为 aaa 的长度, MMM 为 bbb 的长度
01背包
有 NNN 件物品和一个容量是 VVV 的背包。每件物品只能使用一次。
第 iii 件物品的体积是 viv_{i}vi,价值是 wiw_{i}wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,VN,VN,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 NNN 行,每行两个整数 vi,wiv_{i},w_{i}vi,wi,用空格隔开,分别表示第 iii 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤10000<N,V≤1000
0<vi,wi≤10000<v_{i},w_{i}≤10000<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
C++代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010;
int n, m; // n个物品, m的容量
int w[N], v[N]; // 第i个物品的价值和体积
int dp[N][N]; // 第i个物品, 体积为j的背包能装的最大的价值
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
dp[i][j] = dp[i - 1][j]; // 不装
if (j - v[i] >= 0)
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
}
}
cout << dp[n][m] << endl;
return 0;
}
运行截图
算法分析
状态 f[i][j]f[i][j]f[i][j] 定义:前 iii 个物品,背包容量 jjj 下的最优解(最大价值):
两个大状态:
-
不将此物品放入背包中
此时,当前的价值和上一个物品的价值一样
f[i][j]=f[i−1][j]f[i][j] = f[i - 1][j]f[i][j]=f[i−1][j]
-
将此背包放入背包中
-
可以将此物品放入背包中
当前的价值等于
i - 1个物品的j - v[i]的价值加上当前的价值f[i][j]=f[i−1][j−v[i]]+w[i]f[i][j] = f[i - 1][j - v[i]] + w[i]f[i][j]=f[i−1][j−v[i]]+w[i]
-
不可以将此物品放入背包中
直接跳过这个情况即可
-
复杂度分析
所有的背包容量和物品都要遍历一次, 所以时间复杂度为O(N∗V)=O(n2)O(N*V)=O(n^{2})O(N∗V)=O(n2), NNN为物品的个数, VVV为背包的容量
体积优化(滚动数组)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010;
int n, m; // n个物品, m的容量
int w[N], v[N]; // 第i个物品的价值和体积
int dp[N]; // 第i个物品, 体积为j的背包能装的最大的价值
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]);
for (int i = 1; i <= n; i++) {
for (int j = m; j >= v[i]; j--) {
if (j - v[i] >= 0)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[m] << endl;
return 0;
}
优化思路:
可以看到:第 iii 个物品的所有的状态只依赖于第 i−1i - 1i−1 个物品的状态, 不依赖于再之前的状态,所以可以将那些状态抛弃
此时, 由于将状态方程dp由二维转换成一维的了, 所以也要对代码进行等价转换.
复杂度分析
空间复杂度由 O(N∗V)=O(n2)O(N * V)=O(n^{2})O(N∗V)=O(n2) 优化成了 O(V)=O(n)O(V) = O(n)O(V)=O(n)
可以看到优化前后的算法的时间复杂度和空间复杂度的变化还是很明显的:
- 二维版
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zsMOU4dK-1655861947924)(C:\Users\ghost\AppData\Roaming\Typora\typora-user-images\image-20220518203829043.png)]](https://i-blog.csdnimg.cn/blog_migrate/5e39a41e9f5bebde9bd5f34cea288ce7.png)
- 优化版
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kSfYbdSy-1655861947924)(C:\Users\ghost\AppData\Roaming\Typora\typora-user-images\image-20220518203838682.png)]](https://i-blog.csdnimg.cn/blog_migrate/89340097ff3eb5a2828f47a42ed2cf81.png)
额外补充
背包问题状态问题的总结
体积最多是 j
全部初始化成 000 , 算的时候, 要保证体积 v >= 0
体积恰好是 j
初始状态 f(0) = 0, 其余的全部初始化为正无穷, 同时保证体积 v >= 0
体积至少是j
初始状态 f(0) = 0, 其余的全部初始化为正无穷

本文深入探讨了动态规划在解决最长公共子序列和01背包问题中的应用。首先介绍了动态规划的基本思想,通过一个实例展示了如何利用动态规划求解最长公共子序列,接着详细解释了01背包问题的动态规划解决方案,并通过代码实现进行了演示。最后,对算法的时间复杂度进行了分析,并讨论了如何通过空间优化减少复杂度。
1199

被折叠的 条评论
为什么被折叠?



