01背包问题
N个物品,容量为V的背包
物品有两个数学——Vi体积,Wi价值
每件物品最多使用一次——要么使用0次、要么使用1次,最多使用一次
使得背包能装得下的情况下,最大价值为多少?
#include <iostream>
#include <algorithm>
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) // 1~n 总共n个物品
{
cin >> v[i] >> w[i];
}
// 一开始由于f是全局,因此f[0][1~m] 前0个物品使得总体积不超过1~m的状态方程为0
for (int i = 1; i <= n; ++i)
{
for (int j = 0; j <= m; ++j)
{
// 划分两个集合,不包含第i个物品
f[i][j] = f[i - 1][j];
if (j >= v[i])
{
// 含第i个物品,在包含i和不包含i之间选择一个最大值
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
优化
转化为一维:
若j从小到大,f[j-v[i]]中,由于j-v[i]小于j,f[j-v[i]]已经在i这层循环被计算了,而我们想要的f[j-v[i]]应该是i-1层循环里面的,所以j从大到小的话保证此时的f[j-v[i]]还未被计算,也就是第i-1层的数据
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N]; // 体积和价值的数组
int f[N];
int main()
{
int n, m;
cin >> n >> m; // 物品数量和背包容量
for (int i = 1; i <= n; ++i) // 1~n 总共n个物品
{
cin >> v[i] >> w[i];
}
// 一开始由于f是全局,因此f[0][1~m] 前0个物品使得总体积不超过1~m的状态方程为0
for (int i = 1; i <= n; ++i)
{
for (int j = m; j >= v[i]; --j) // if条件原本要满足j >= v[i],因此j范围在0~v[i - 1]是没有用的,可以直接不考虑
{
// 由于f[i]只用到了上一层f[i - 1],因此可以直接变为1维
/*
f[i][j] = f[i - 1][j]; -> f[j] = f[j];
*/
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m] << endl;
return 0;
}
完全背包
每件物品有无限个,只要体积够用就可以无限装
O(n^3)初始版本
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];
int m, n;
int main()
{
cin >> m >> n;
for (int i = 1; i <= m; ++i)
{
cin >> v[i] >> w[i];
}
for (int i = 1; i <= m; ++i) // 第i个物品
{
for (int j = 0; j <= n; ++j) // j空间
{
for (int k = 0; k*v[i] <= j; ++k) // 选k个第i个物品
{
f[i][j] = max(f[i][j], f[i - 1][j - k*v[i]] + k*w[i]);
// 优化前:① max(f[i][j], f[i - 1][j - k*v[i]] + k*w[i]);
// 而不是:② max(f[i - 1][j], f[i - 1][j - k*v[i]] + k*w[i]);
// k = 0时的情况,其实就是 ① 的情况 ,因为初识数组是全局默认全为0
// 因此一开始一定是先f[i][j] = max(f[i][j], f[i - 1][j]) -> k = 0
// 因此在之后的过程中一定会将f[i][j]进行更新 如果写②的情况下,不一定会保证每次都把它往大了更新
}
}
}
cout << f[m][n] << endl;
return 0;
}
如何将他优化为二维的?
f[i][j] = f[i-1][j-v[i]*k] + w[i]*k展开:
①f[i][j] = max(f[i-1,j], f[i-1][j-v]+w,f[i-1][j-2v]+2w,f[i-1][j-3v]+3w,…)
②f[i,j-v] = max( f[i-1][j-v], f[i-1,j-2v]+w, …)
对于下面这个式子解释:包含一个物品,f[i-1,j-2v]
这样子就会发现,f[i][j]的最大值比f[i][j-v]大w
就可以优化为:f[i,j] = max(f[i-1][j], f[i][j-v]+w)
因此从枚举k个优化到枚举2个了
对比01背包,01背包是从i-1转移过来,完全背包是从i转移过来
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];
int m, n;
int main()
{
cin >> m >> n;
for (int i = 1; i <= m; ++i)
{
cin >> v[i] >> w[i];
}
for (int i = 1; i <= m; ++i) // 第i个物品
{
for (int j = 0; j <= n; ++j) // 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[m][n] << endl;
return 0;
}
优化到一维:——参考01背包
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N];
int m, n;
int main()
{
cin >> m >> n;
for (int i = 1; i <= m; ++i)
{
cin >> v[i] >> w[i];
}
for (int i = 1; i <= m; ++i) // 第i个物品
{
for (int j = v[i]; j <= n; ++j) // j空间
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[n] << endl;
return 0;
}
多重背包
每个物品最多有Si个,不同物品Si可能不一样
思路与完全背包一样
#include <iostream>
using namespace std;
const int N = 110;
int v[N], w[N], s[N];
int f[N][N];
int main()
{
int m, n;
cin >> m >> n;
for (int i = 1; i <= m; ++i)
{
cin >> v[i] >> w[i] >> s[i];
}
for (int i = 1; i <= m; ++i)
{
for (int j = 0; j <= n; ++j)
{
for (int k = 0; k <= s[i] && k*v[i] <= j; ++k)
{
f[i][j] = max(f[i][j], f[i - 1][j - k*v[i]] + k*w[i]);
}
}
}
cout << f[m][n] << endl;
return 0;
}
使用二进制方法进行优化
#include <iostream>
using namespace std;
const int N = 20100;
int f[N];
int v[N], w[N];
int main()
{
int n, m;
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) // 只要该分法还没有达到最大值,就一直分下去,分到最后要么与s相等,要么还相差常数c个
{
cnt++; // 最后新的物品容量——相当于有cnt个篮子
v[cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2;
}
if (s > 0) // 如果还有剩余
{
cnt++;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
n = cnt;
for (int i = 1; i <= n; ++i)
{
for (int j = m; j >= v[i]; --j) // 01背包问题
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m] << endl;
return 0;
}