ACwings的模板中,j从0开始枚举,如果之后出错可试试看
01背包
取或不取,一件物品最多用一次
状态表示
取或不取两种情况 其状态表示就是取了哪些
不同的取法构成集合,具体而言,是从1~i中的不同取法,满足不超过背包容量的条件的集合,要使取法的价值最大
状态计算
分为两类,取第i件和不取第i件
- 不取的计算可以看作从 1~i-1 里取,使得价值最大
- 取则可以看作是,先不取,在1~i-1中取不超过总重量-第i件重量的物品,再取第i件,使得价值最大
实现
抽象实现,有m的重量可以装,可以从0开始遍历所有的可能重量;有n个物品可以装,每一个情况都分为有没有第i个两种选择,用递推做
由于初始值,选0个时值是确定的,且重量从0开始增加,即当i+1时,i的所有值已经算出来了,所以可以递推
int v[N], w[N];
int f[N][N];
int main()
{
int n, m;
cin >> n >> m;
//f[0][1~m] = 0 取0件
//从取0件开始,用递推推出每种选择的结果
//从头遍历找到最后答案
for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i];
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
{
f[i][j] = f[i - 1][j]; // f[i][j]指这个值的最终结果,这一部先算,再考虑是这部分大还是另一个分类大
if(v[i] <= j) f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
}
cout << f[n][m] << endl;
return 0;
}
一维优化
思路是,将原本是二维的数组变成一维 依据:每次计算只会用到上组计算的值,而不会用到之前多组,这意味着可以覆盖着用上组的计算结果来求这组的计算结果,即转为一维
int f[N];//优化到一维
for(int i = 1; i <= n; i ++)
//for(int j = 1; j <= m; j ++) 算到i的时候i-1已经被覆盖了
/*如果第i件放不下,则计算结果和上一组没区别
for(int j = m; j > 0; j --)
{
if(v[i] <= j) f[j] = max(f[j], f[j - v[i]] + w[i]);
}*/
//逆过来算,防止被覆盖掉
for(int j = m; j >= v[i]; j --)
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
这种方法称为 滚动数组
完全背包
取多少件都可以,只要不超重
状态表示
和01背包一致
状态计算
如何分类计算 分为:第i个选0~k的多种情况,计算最大值,计算方法一致,先去掉k个,再加上k个
实现
// 基本代码
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
for(int k = 0; k * v[i] <= j; k ++)
{
// 因为k进来的条件保证了j>=vi
//这里不再是i-1,是因为取不取第i件的情况都在循环里
//因为还有k循环,语句只是在k去不同的值里找到最大值
f[i][j] = max(f[i][j], f[i-1][j - k*v[i]] + k * w[i]);
}
优化为两维
思路是: 发现上下两式差一个w(除上式第一项) 即可以化为
for(int i = 1; i <= n; i ++)
for(int j = 1; 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 * w[i]);
}*/
{
f[i][j] = f[i - 1][j]; //如果判断没过,i拿不了,记录当前值为上一个的值
if(v[i] <= j) f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i]);
}
一维
和01背包一致,只是这次的优化里需要用到的数没有被提前覆盖掉,不用逆序
或者理解为最后在哪里取
for(int i = 1; i<= n; i ++)
/*改
for(inr j = 1; j <= m; j ++)
{
f[i][j] = f[i-1][j];
if(v[i] <= j) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
}*/
for(int j = v[i]; j <= m; j ++)
//f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
//这里的计算不会与之前的计算冲突
f[j] = max(f[j], f[j - v[i]] + w[i]);
多重背包
每件物品数量有上限,怎么选价值最大
状态表示
从1~i件物品中选的物品的组合,要找到最大价值的选择
状态计算
划分为第i件物品选 0、1、2 …… s(or 超重)件来计算
选0件,就是在 i - 1 中选;选其他的就是先选 i - 1 中不超过 总重量 j 减去 k 件第i件的重量的最大值再加上 k 件 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 - k * v[i]] + k * w[i]); //如果第i件拿不了,那f[i][j] = f[i-1][j]
}
二进制优化
将每个物品的上限分为二进制的数的组合 例如 上限 s = 1023 分为 1,2,4,8…… 512 就可以类似于二进制数将所有能表示的数分组表示出来
如果不是刚刚好可以分为2的倍数呢?将最后一个数设为 ,由于之前的2的倍数可以组合成范围内所有数,那这些数分别与就可以组合成s以内的所有数了(这样会有一些数重复表示,但是已经是很大的优化了)
分组之后就是 01背包问题
//注意范围,将一个数分为二进制数后,分组数增加
const int N = 12000;
int v[N], w[N], f[N];
int main()
{
int n, m;
cin >> n >> m;
int cnt = 0;
for(int i = 0; i < n; i ++)
{
int a, b, s;
cin >> a >> b >> s;
//二进制优化
int k = 1;
while(k <= s)
{
cnt ++;
v[cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2;
}
//要是s还有
if(s)
{
cnt ++;
v[cnt] = s * a;
w[cnt] = b * s;
}
}
n = cnt; //更新n
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]);
cout << f[m] << endl;
return 0;
}
分组背包
分组,每组最多选一个
状态表示
所有选法的集合里,要一个最大价值
状态计算
将第i组的选择分为不选第i组和选第i组中的第几个 不选:就是在1~i-1组中选的最大值 选:按每组的不同选择计算最大值,要求选了之后不超重
实现
int v[N][N], w[N][N], s[N];
int f[N];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> s[i];
for(int j = 1; j <= s[i]; j ++)
cin >> v[i][j] >> w[i][j];
}
//直接化成 1 维,因为每次要用前面的数来计算(j - v[i][k]),所以 j 要倒着算才不会覆盖掉前面的数
for(int i = 1; i <= n; i ++)
for(int j = m; j >= 0; j --)
for(int k = 1; 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;
}