总结
1.背包问题
1)01背包(物品选 或 不选)
先想二维,再优化为一维。
二维code
#include <iostream>
using namespace std;
const int N = 1010;
int v[N], w[N]; //体积数组v, 价值数组w
int f[N][N]; //二位状态f[i][j] : 前i个物品,总体积不超过j的最大价值。
int main()
{
int m, n;
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
//f[0][0~m] = 0 //前0个物品,总体积……的最大价值为0,因为没有物品可选
for(int i = 1; i <= n; i++)
for(int j = 0; j <= m; j++)
{//集合的思想
f[i][j] = f[i-1][j]; //不包含第i个物品
if(j >= v[i]) f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]); //包括第i个物品
}
cout << f[n][m] << endl;
return 0;
}
二维数组f[i][j]的数值
优化:一维code
#include <iostream>
using namespace std;
const int N = 1010;
int v[N], w[N]; //体积数组v, 价值数组w
int f[N]; //二位状态f[i][j] : 前i个物品,总体积不超过j的最大价值。
//滚动数组
int main()
{
int m, n;
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 = m; j >= v[i]; j--)
f[j] = max(f[j], f[j-v[i]] + w[i]); //包括第i个物品
cout << f[m] << endl;
return 0;
}
2)完全背包(每个物品 可以选 无限次)
推导过程
朴素版code(状态划分为 【不选】,【选1个】,【选2个】,……直到体积超过可选范围)
#include<iostream>
using namespace std;
const int N = 1010;
//一样的变量
int v[N], w[N];
int f[N][N];
int main()
{
int n, m;
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 = 0; j <= m; j++)
for(int k = 0; 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;
return 0;
}
优化版
// 推导过程:
// step1.根据正常【状态划分】推导:
// f[i][j] = max(f[i-1][j], f[i-1][j-v[i]*1]+w[i]*1, f[i-1][j-v[i]*2]+w[i]*2, f[i-1][j-v[i]*3]+w[i]*3, …………)
// 针对第i件物品:不选 选一件 选两件 选三件 …………
// step2.凑出一个式子,来化简上面这个长式子
// f[i][j-v[i]] //我觉得是凑出来的,接下来根据【状态划分】思路对f进行划分
// f[i][j-v[i]]=max( f[i-1][j-v[i]], f[i-1][j-v[i]-v[i]*1]+w[i], f[i-1][j-v[i]-v[i]*2]+w[i]*2, ……)
// 针对第i件物品,总体积不超过j-v[i]:不选(比上面少1个w), 选一件(比上面少1个w), 选两件(比上面少1个w)
// step3.合并上面两式子
// f[i][j] = max(f[i-1][j],f[i][j-v[i]]+w[i]);
// 说明: step1的第一项 step2的整体+w[i]
#include<iostream>
using namespace std;
const int N = 1010;
//一样的变量
int v[N], w[N];
int f[N][N];
int main()
{
int n, m;
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 = 0; j <= m; j++)
{
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;
return 0;
}
一维跟01背包一样 化简下标就可以(只不过01背包是反着写,完全背包是正着写!)
总结:01背包和完全背包 状态转移方程的区别
01:f[i][j] = max(f[i][j], f[i-1][j-v[i]]+w[i]);
一维 反着写
完全:f[i][j] = max(f[i][j], f[i][j-v[i]]+w[i]);
一维 正着写
完全背包靠那个式子推导出来的,跟当前i有关,所以要正着写
而01背包根据状态划分得到的式子 跟i-1有关,避免使用已经计算过的数值,所以要逆着写
3)多重背包(物品次数给定)
朴素版(暴力求解,枚举选0次、1次、直到k次)
#include<iostream>
using namespace std;
const int N = 110;
int v[N], w[N], s[N];
int f[N][N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i] >> 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++) //枚举次数,条件:不超过k次,并且体积不大于j
f[i][j] = max(f[i][j], f[i-1][j-k*v[i]]+w[i]*k);
cout << f[n][m] << endl;
return 0;
}
优化