0-1背包问题、最大连续子数组问题、最长公共子序列、最长公共子串、最小编辑距离、钢条切割、矩阵链乘
0-1背包问题、最大连续子数组问题、最长公共子序列、最长公共子串、最小编辑距离、钢条切割、矩阵链乘
动态规划问题的一般步骤:
- 给出问题的表示,明确子问题
- 分析最优结构,构造递推公式
- 确定计算顺序,依次求解问题
- 记录决策过程,输出最优方案
0-1背包
动规方程:
用\(p[i,c]\)表示前i个物品在背包容量为c时能选择的最大价值。
\(dp[i,c] = max\{dp[i-1,c-v_i] + p_i, dp[i-1,c]\}\)
所以0-1背包问题就是求\(p[n,C]\)
伪代码:
//初始化
for(i=0 to C) { //前0个物品,背包容量是C时
dp[0][i] = 0;
}
for(i=0 to n) { //前i个物品,背包容量是0时
dp[i][0] = 0;
}
//i表示前i个物品
for(i=1 to n) {
//j表示当前背包容量
for(j=1 to C) {
// 如果选择这个物品装入背包
if ((v[i] < C) and ( dp[i-1][j-v[i]] + p[i] > dp[i-1][j])){
dp[i][j] = dp[i-1][j-v[i]] + p[i];
}
// 如果背包容量装不下v[i]或者装入背包后总价值变小,那么选择不装入
else {
dp[i][j] = dp[i-1][j];
}
}
}
// 选择最优的方案
return dp[n][C];
时间复杂度:\(O(n*c)\)
总结,解决0-1背包问题的思路:
- 有n种物品,其对应的体积为\(v_i\), 价值为\(p_i\),想要求容量为C的背包,能装下物品的最大价值,用dp[i][j]表示前i种物品,在背包容量为j的情况下能装下物品的最大价值
- 构造递推式,子问题和原问题之间的关系,\(dp[i,c] = max\{dp[i-1,c-v_i] + p_i, dp[i-1,c]\}\)
- 确定计算的顺序,就是从前往后递推
- 记录...
最大连续子数组问题
dp[i]表示以X[i]开头的最大子数组的和,所以要从后往前计算
如果用dp[i]表示以X[i]结尾的最大子数组的和,则从前往后计算
dp[n] = X[n]
Rec[n] = n; // 用来记录选择了哪些元素
for(i=n-1 to 1) {
//如果以X[i+1]开头的最大子数组的和dp[i+1]大于0,那么以X[i]开头的最大子数组的和dp[i]等于X[i]+ dp[i+1]
if(dp[i+1] > 0) {
dp[i] = dp[i+1] + X[i];
Rec[i] = Rec[i+1]; //
}
else {
dp[i] = X[i];
Rec[i] = i;
}
}
// 遍历子数组,得到最大子数组的和、选择的元素
Smax = dp[1];
for(i=2 to n) {
if (Smax < dp[i]) {
Smax = dp[i];
l = i;
r = Rec[i];
}
}
return Smax, l , r;
时间复杂度\(O(n)\)
最长公共子序列
原始问题:X[1..n] Y[1..m]两个字符串,求其最长公共子序列
用dp[i][j]表示X[1..i], Y[1..j]的最长公共子序列长度
递推式:
\(if X[i] == Y[j]: dp[i][j] = dp[i-1][j-1] + 1;\)
\(else: dp[i][j] = max\{dp[i][j-], dp[i-1][j]\}\)
LCS(){
// 初始化
for(i=0 to n) {
dp[i][0] = 0;
}
for(j=0 to m) {
dp[0][j] = 0;
}
rec[0..n][0..m]; // 用来记录选择的子串
for(i=1 to n) {
for(j=1 to m) {
// X[i] == Y[j]
if(X[i] == Y[j]) {
dp[i][j] = dp[i-1][j-1] + 1;
rec[i][j] = 'LU'; //来自表格的左上
}
// X[i] != Y[j]
else if(dp[i-1][j] >= dp[i][j-1]) {
dp[i][j] = dp[i-1][j];
rec[i][j] = 'U'; //来自表格的上面
}
else {
dp[i][j] = dp[i][j-1];
rec[i][j] = 'L' //来自表格的左边
}
}
}
}
PrintLCS(rec[][], i, j)
{
if(i==0 or j==0) {
return NULL;
}
if(rec[i][j] == 'LU') { // 只有这里是相等的,需要打印一个字符
PrintLCS(rec, i-1, j-1);
print(x[i]);
}
else if(rec[i][j] == 'L') {
PrintLCS(rec, i, j-1);
}
else if(rec[i][j] == 'U') {
PrintLCS(rec, i-1, j)
}
}
时间复杂度\(O(n*m)\)
最长公共子串
子序列不要求连续,但是子串要求是连续的
递推式:
\(if X[i] == Y[j]: dp[i][j] = dp[i-1][j-1] + 1;\)
\(else: dp[i][j] = 0\)
伪代码:
// 初始化
for(i=1 to n) {
dp[i][0] = 0;
}
for(j=1 to m) {
dp[0][j] = 0;
}
//
for(i=1 to n) {
for(j=1 to m) {
if(X[i] == Y[j]) {
dp[i][j] = dp[i-1][j-1] + 1;
}
else {
dp[i][j] = 0;
}
}
}
return dp[n][m]
时间复杂度\(O(n*m)\)
最小编辑距离
有点复杂,最后复习
下面这两个是同一种类型的,都需要遍历子问题
钢条切割
C[i]表示切割长度为i的钢条所能获得的最大收益
$C[i] = max_{1\le j\le i-1}{p[j] + C[i-j]], p[i]} $
c[0] = 0;
for(i=1 to n) {
q = p[i]; // 不切割
for(j=1 to i-1) { // 枚举子问题,找最大值
if (p[j]+C[i-j] > q) {
q = p[j] + C[i-j]
}
}
C[i] = q;
}
return C[n]
时间复杂度为:\(O(n^2)\)
矩阵链乘
明确原始问题:
明确原始问题