动态规划步骤
1、确定状态变量(函数)
2、确定状态转移方程(递推关系)
3、确定边界条件
4、确定递推顺序
闫氏dp分析法:
图片@_snowstorm_
一、01背包问题
状态变量:f[i][j]表示前i件物品放入容量为j的背包的最大价值
优化空间复杂度前模板 (时间复杂度O(nm),空间复杂度(nm))
#include<iostream>
using namespace std;
const int N = 1010;
int n,m;
int w[N],v[N],max_value[N][N];
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i ++ ){
cin >> w[i] >> v[i];
}
for(int i = 1;i <= n;i ++ ){
for(int j = 1;j <= m;j ++ ){ //判断当背包容量为j时,第i件物品能否放入,是否放入
if(j < w[i]) max_value[i][j] = max_value[i - 1][j]; /* j < w[i],当前背包容量j放不下第i件物品,
max_value[i][j] = max_value[i - 1][j],价值不变。 */
else max_value[i][j] = max(max_value[i - 1][j],max_value[i - 1][j - w[i]] + v[i]); /* 当背包容量可以放下物品时,如果不让第i件物品放入背包,则max_value[i][j] = max_value[i - 1][j],
如果让第i件物品放入背包,则背包容量还剩j - w[i],此时放入物品背包容量为max_value[i - 1][j - w[i]]。
取这两种情况的最大值即为前i件物品放入容量为j的背包时的最大价值。 */
}
}
cout << max_value[n][m];
return 0;
}
优化空间复杂度后模板(时间复杂度O(nm),空间复杂度O(m))
如果我们用一维数组max_value[j]只记录一行数据,让j值顺序循环,顺序更新max_value[j],此时max_value[j - w[i]] 会先于max_value[j]更新,也就是说,用新值max_value[j - w[i]] 去更新max_value[j]会出错。此时我们选择逆序遍历max_value[j],因为j是逆序循环,max_value[j]会先于max_value[j - w[i]]更新,也就是说,用旧值max_value[j - w[i]] 去更新max_value[j],相当于上一行(上一个i)的max_value[j - w[i]] 去更新max_value[j],就不会出错。
第二层循环第二个参数将j > 0改为 j > w[i] 可保证[j - w[i]]为正数。
#include<iostream>
using namespace std;
const int N = 1010;
int n,m;
int w[N],v[N],max_value[N];
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i ++ ){
cin >> w[i] >> v[i];
}
for(int i = 1;i <= n;i ++ ){
for(int j = m;j >= w[i];j -- ){
max_value[j] = max(max_value[j],max_value[j - w[i]] + v[i]);
}
}
cout << max_value[m];
return 0;
}
二、完全背包问题
朴素做法
#include<iostream>
using namespace std;
const int N = 1010;
int n,m;
int w[N],v[N];
int max_value[N][N];
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i ++ ){
cin >> w[i] >> v[i];
}
for(int i = 1;i <= n;i ++ ){
for(int j = 1;j <= m;j ++ ){
if(j < w[i]){
max_value[i][j] = max_value[i - 1][j];
}
else max_value[i][j] = max(max_value[i - 1][j],max_value[i][j - w[i]] + v[i]);
}
}
cout << max_value[n][m];
return 0;
}
优化空间复杂度后
#include<iostream>
using namespace std;
const int N = 1010;
int n,m;
int w[N],v[N],max_value[N];
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i ++ ){
cin >> w[i] >> v[i];
}
for(int i = 1;i <= n;i ++ ){
for(int j = w[i];j <= m;j ++ ){
max_value[j] = max(max_value[j],max_value[j - w[i]] + v[i]);
}
}
cout << max_value[m];
return 0;
}
完全背包的代码即是顺序递推的01背包的代码,虽然代码只做了很简单的改变,但改变其中的原理逻辑并不简单,需要推导和理解。
三、多重背包问题awdaskjdnaoiwdaolsjdnauoiwdhaiyusd
#include<iostream>
using namespace std;
const int N = 1010;
int n,m;
int w[N],v[N],s[N],max_value[N][N];
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i ++ ){
cin >> w[i] >> v[i] >> s[i];
}
for(int i = 1;i <= n;i ++ ){
for(int j = m;j >= 0;j -- ){
for(int k = 0;k <= s[i] && k * w[i] <= j;k ++ ){
max_value[i][j] = max(max_value[i][j],max_value[i - 1][j - k * w[i]] + k * v[i]);
}
}
}
cout << max_value[n][m];
return 0;
}
多重背包问题的二进制优化
#include<iostream>
using namespace std;
const int N = 11010;
int n,m;
int v,w,s;
int f[N],vv[N],ww[N];
int main(){
int num = 1;
cin >> n >> m;
for(int i = 1;i <= n;i ++ ){
cin >> v >> w >> s;
for(int j = 1;j <= s;j <<= 1){
vv[num] = j * v;
ww[num ++ ] = j * w;
s -= j;
}
if(s){
vv[num] = s * v;
ww[num ++ ] = s * w;
}
}
int nn = num;
for(int i = 1;i <= nn;i ++ ){
for(int j = m;j >= vv[i];j -- ){
f[j] = max(f[j],f[j - vv[i]] + ww[i]);
}
}
cout << f[m];
return 0;
}
四、分组背包
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 110;
int n,m;
int v[N],w[N],f[N];
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i ++ ){
int s;
cin >> s;
for(int j = 0;j < s;j ++ ){
cin >> v[j] >> w[j];
}
for(int k = m;k >= 0;k -- ){
for(int l = 0;l < s;l ++ ){
if(k >= v[l]) f[k] = max(f[k],f[k - v[l]] + w[l]);
}
}
}
cout << f[m] << endl;
return 0;
}