赞
踩
题目介绍
输入样例
4 5
1 2
2 4
3 4
4 5
输出
8
[版本一] 0-1背包问题的二维状态定义
f[i][j] : 只选前i个物品, 总体积 <= j 的 Max
状态分析
(1) 当前背包容量不够时(j < v[i]),选不了第 i 个物品,因此前i个物品的最优解和前 i - 1一样
f[i][j] = f[i - 1][j]
(2) 当前背包容量够第 i 个时(j ≥ v[i]),可选第 i 个物品,因此需要继续决策 选 or 不选 第i个物品
f[i][j] = f[i - 1][j - v[i] + w[i]
f[i][j] = f[i - 1][j]
状态转移方程:
f[i][j] = max(f[i-1][j], f[i-1][j-v[i]] + w[i]
C++ 代码如下
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 1010; int n, m; int v[N]; // 体积 int w[N]; // weight int f[N][N]; // DP数组 // f[i][j] : 只选前i个物品, 总体积 <= j int main(){ cin >> n >> m; for(int i = 1; i <= n; i++) cin >> v[i] >> w[i]; // 二维f[i][j] for(int i = 1; i <= n; i++){ for (int j = 0; j <= m; j ++ ){ f[i][j] = f[i-1][j]; // 对应情况1 if(j >= v[i]) // j-v[i] >= 0 对应情况2, 并决策 f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]); } // !!! f[i][j] = max(f[i-1][j], f[i-1][j-v[i]] + w[i]) } cout << f[n][m] <<endl; return 0; }
[版本二——最终版本] 0-1背包问题的一维状态
举个栗子:
假如枚举到:i = 3, j = 8, v[3] = 5, w[3] = 1
二维:dp[3][8] = max(dp[2][8], dp[2][3] + w[3]) 此时的dp[2][8]和dp[2][3]都是上一轮的状态值
一维:dp[8] = max(dp[8], dp[3] + w[3]) 优化一维, 需要保证dp[8]和dp[3]都是上一轮的状态值
如果顺序枚举j:(第i轮) 当j = 8时, j = 3 已经枚举过了,
dp[3] 已经计算过了, 因此 dp[3] 是第i轮的状态, 而不是第i-1轮的状态 ×
如果逆序枚举j: (第i轮) 当j = 8时, j = 3 还未枚举
dp[3] 还未计算, 因此 dp[3] 是第i-1轮的状态 √
C++ 代码如下
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 1010; int n, m; int v[N]; // 体积 int w[N]; // weight int f[N]; // DP数组 // f[j] : 总体积 <= j int main(){ cin >> n >> m; for(int i = 1; i <= n; i++) cin >> v[i] >> w[i]; // 优化: 一维f[j] v[i] <= j <= V for(int i = 1; i <= n; i++){ for(int j = m; j >= v[i]; j--){ // [注] j需要逆序 f[j] = max(f[j], f[j-v[i]] + w[i]); } //f[i][j] = max(f[i-1][j], f[i-1][j-v[i]] + w[i]) } cout << f[m] <<endl; return 0; }
题目描述
输入样例
4 5
1 2
2 4
3 4
4 5
输出
10
状态分析
版本一、暴力 (TLE)
for(int i = 1; i <= n; i++)
for(int j = 0; j <= m; j++)
for(int k = 0; k * v[i] <= j; k++)
f[i][j] = max(f[i][j], f[i-1][j - k*v[i]] + k*v[i]);
cout<< f[n][m] <<endl;
因此需要优化状态转移方程
原 : f[i , j ] = max(f[i-1,j], f[i-1,j-v]+w, f[i-1,j-2*v]+2*w, f[i-1,j-3*v]+3*w , .....)
又∵f[i , j-v]= max(-----------f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
由上两式,可得出如下递推关系:
f[i][j] = max(f[i - 1][j], f[i][j - v] + w)
对比 0-1 背包的二维转移方程, 只有下标不同
f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w)
版本二、优化后的二维状态方程
代码如下:
for(int i = 1; i <= n; i++){
for(int j = 0; j <= m; j++){
// f[i][j] = max(f[i-1][j], f[i-1][j-v] + w) 0-1背包
// f[i][j] = max(f[i-1][j], f[i][j-v] + w) 完全背包
f[i][j] = f[i-1][j];
if(j >= v[i]) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
}
}
cout<< f[n][m] <<endl;
版本三——最终版:一维状态方程
完整代码如下
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 1010; int v[N], w[N]; int f[N]; int n, m; int main(){ cin >> n >> m; for(int i = 1; i <= n; i++) cin >> v[i] >> w[i]; for(int i = 1; i <= n; i++){ for(int j = v[i]; j <= m; j++){ f[j] = max(f[j], f[j - v[i]] + w[i]); } } cout<< f[m] <<endl; return 0; }
题目描述
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出
10
状态分析
版本一、暴力法
f[i][j] = max(f[i-1][j-v[i] * k] + w[i] * k)
,k 为选的个数,范围从 0 到 s[i]
for(int i = 1; i <= n; i++)
for (int j = 0; j <= m; j ++ )
for (int k = 0; k <= s[i] && k * v[i] <= j; k ++ )
f[i][j] = max(f[i][j], f[i-1][j-v[i] * k] + w[i] * k);
cout << f[n][m] << endl;
版本二、利用分组二进制的思想优化
1, 2, 4,...,2^k, c 共 log(s) + 1 组
s == 1 + 2 + 4 + ... + 2^k + c
代码如下
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 24000, M = 2010; int n, m; int v[N], w[N], s[N]; int f[M]; int cnt = 0; int main(){ cin >> n >> m; int cnt = 0; //分组数 for(int i = 1; i <= n;i ++) { int a, b, s; cin >> a >> b >> s; int k = 1; // 组别里的个数 while(k <= s) { cnt ++ ; v[cnt] = a * k ; // V w[cnt] = b * k; // W s -= k; // s - 2^k k *= 2; // k = k * 2 } //剩余不满足2^k的一组 if(s > 0) { cnt ++ ; v[cnt] = a * s; w[cnt] = b * s; } } // for(int i = 1; i <= cnt; i++){ // cout << v[i] <<" "<< w[i] <<endl; // } // 分完组后, 每个组相当于0-1背包中的一个物品 // 0-1背包 一维优化 for(int i = 1; i <= cnt; i++) for(int j = m; j >= v[i]; j-- ) f[j] = max(f[j], f[j-v[i]] + w[i]); cout << f[m] << endl; return 0; }
题目描述
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出
8
状态分析
从第
i
组选一个
,
总体积不超过
j
从第 i 组选一个, 总体积不超过 j
从第i组选一个,总体积不超过j
状态转移方程 :
f[i][j] = max(f[i-1][j], f[i-1][j-v[i][k]] + w[i][k])
[注]
代码如下
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 105; int f[N]; // f[i][j] : 从第 i 组选1个, 体积不超过 j int v[N][N], w[N][N]; int s[N]; // 组数 int n, m; int main(){ cin >> n >> m; for(int i = 1; i <= n; i ++){ // n : 总组数 cin >> s[i]; // s[i] : 组内个数 for(int j = 0; j < s[i]; j++){ cin >> v[i][j] >> w[i][j]; // [第i组] [第j个] } } for(int i = 1; i <= n; i++) for(int j = m; j >= 0; j--) for(int k = 0; k < s[i]; k++) // 遍历组数 if(v[i][k] <= j) f[j] = max(f[j], f[j-v[i][k]] + w[i][k]); cout << f[m] <<endl; return 0; }
状态分析
f
[
i
]
[
j
]
表示从
(
1
,
1
)
到
(
i
,
j
)
所有方案的集合
f[i][j]表示从(1,1)到(i,j)所有方案的集合
f[i][j]表示从(1,1)到(i,j)所有方案的集合
状态转移方程
f[i][j] = max(f[i-1][j-1], f[i-1][j]) + a[i][j]
代码
#include <iostream> #include <cstring> #include <algorithm> #include <climits> using namespace std; const int N = 510; int f[N][N]; int a[N][N]; int main(){ int n; cin>>n; for(int i = 1; i <= n; i++){ for(int j = 1; j <= i; j++){ cin >> a[i][j]; } } for(int i = 0; i <= n; i++){ for(int j = 0; j <= i + 1; j++){ f[i][j] = INT_MIN; // 初始化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], f[i-1][j]) + a[i][j]; } } int ans = INT_MIN; for(int j = 1; j <= n; j++){ ans = max(ans, f[n][j]); } cout << ans << endl; return 0; }
状态分析
f
[
i
]
表示以第
i
个数结尾的上升子序列的
m
a
x
长度
f[i]表示以第i个数结尾的上升子序列的max长度
f[i]表示以第i个数结尾的上升子序列的max长度
状态转移方程
f[i] = max(f[i], f[j] + 1) , j ∈ [0, i-1]
代码
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 1010; int n; int a[N], f[N]; int main(){ cin >> n; for(int i = 1; i <= n; i++) cin >> a[i]; for(int i = 1; i <= n; i++){ f[i] = 1; for(int j = 1; j < i; j++){ if(a[j] < a[i]) f[i] = max(f[i], f[j] + 1); } } int ans = 0; for(int i = 1; i <= n; i++){ ans = max(ans, f[i]); } cout << ans << endl; return 0; }
LeetCode代码:LeetCode 300. 最长递增子序列
class Solution { public: int lengthOfLIS(vector<int>& nums) { int n = nums.size(); int f[n+1]; for(int i = 0; i < n; i++){ f[i] = 1; for(int j = 0; j < i; j++){ if(nums[j] < nums[i]) f[i] = max(f[i], f[j] + 1); } } int ans = 0; for(int i = 0; i < n; i++){ ans = max(ans, f[i]); // cout << f[i] << " " << ans << endl; } return ans; } };
推荐一个讲得比较好的视频 最长公共子序列LCS
状态分析
f
[
i
]
[
j
]
表示
:
s
1
[
0
−
i
]
和
s
2
[
0
−
j
]
的
m
a
x
公共子序列长度
f[i][j]表示: s_1[0-i] 和s_2[0-j]的max公共子序列长度
f[i][j]表示:s1[0−i]和s2[0−j]的max公共子序列长度
图源上述视频
s1[i] == s2[j]
s1[i] != s2[j]
状态转移方程
s1[i] == s2[j], dp[i][j] = dp[i-1][j-1] + 1
s1[i] != s2[j], dp[i][j] = max(dp[i-1][j], dp[i][j-1])
代码:
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 1050; int n, m; char a[N], b[N]; int f[N][N]; int main(){ cin >> n >> m; cin >> a + 1 >> b + 1; for(int i = 1; i <= n; i++){ for(int j = 1; j <= m; j++){ f[i][j] = max(f[i-1][j], f[i][j-1]); if(a[i] == b[j]){ f[i][j] = max(f[i][j], f[i-1][j-1] + 1); } } } cout << f[n][m] << endl; return 0; }
LeetCode代码:1143. 最长公共子序列
注意:下标从0开始
class Solution { public: int longestCommonSubsequence(string t1, string t2) { int n = t1.size(), m = t2.size(); int f[n+1][m+1]; memset(f, 0, sizeof(f)); for(int i = 0; i < n; i++){ for(int j = 0; j < m; j++){ if(t1[i] == t2[j]){ f[i+1][j+1] = f[i][j] + 1; }else f[i+1][j+1] = max(f[i][j+1], f[i+1][j]); } } return f[n][m]; } };
空间优化:滚动数组(2个一维)
class Solution { public: int longestCommonSubsequence(string t1, string t2) { int n = t1.size(), m = t2.size(); int f[2][m+1]; memset(f, 0, sizeof(f)); for(int i = 0; i < n; i++){ for(int j = 0; j < m; j++){ if(t1[i] == t2[j]){ f[(i+1)%2][j+1] = f[i%2][j] + 1; }else f[(i+1)%2][j+1] = max(f[i%2][j+1], f[(i+1)%2][j]); } } return f[n%2][m]; } };
空间优化:一维数组
【注意】在使用一维数组的情况下,由于f[i+1][j+1
]是由f[i][j]
、f[i+1][j]
、f[i][j+1]
(左上、左、上)转移过来的,所以需要用临时变量t来保存左上状态(f[i][j]
),否则f[i][j]
会被f[i+1][j]
覆盖。
class Solution { public: int longestCommonSubsequence(string t1, string t2) { int n = t1.size(), m = t2.size(); int f[m+1]; memset(f, 0, sizeof(f)); for(int i = 0; i < n; i++){ int pre_i_j = 0; for(int j = 0; j < m; j++){ int t = f[j+1]; if(t1[i] == t2[j]){ f[j+1] = pre_i_j + 1; }else f[j+1] = max(f[j+1], f[j]); pre_i_j = t; } } return f[m]; } };\
状态转移方程
s[i] == t[j], dp[i][j] = dp[i-1][j-1]
s[i] != t[j], dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1
代码
class Solution { public: int minDistance(string s, string t) { int n1 = s.size(), n2 = t.size(); int dp[n1+1][n2+1]; memset(dp, 0, sizeof(dp)); // dp[i][j]:从 [长度为i的字符串s] 转换为 [长度为j的字符串t] 的最小操作次数 // 边界条件1:当 len(s) = 0, len(t) = j时, dp[0][j] = j; // 边界条件2:当 len(s) = i, len(t) = 0时, dp[i][0] = i; for(int j = 0; j <= n2; j++) dp[0][j] = j; for(int i = 0; i <= n1; i++) dp[i][0] = i; for(int i = 0; i < n1; i++){ for(int j = 0; j < n2; j++){ if(s[i] == t[j]){ dp[i+1][j+1] = dp[i][j]; // 无需操作 }else{ // 插入 || 删除 || 更改 + 1 (s插入一个字符 等价于 t删除一个字符) dp[i+1][j+1] = min({dp[i+1][j], dp[i][j+1], dp[i][j]}) + 1; } } } return dp[n1][n2]; } };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。