哈喽哈喽这里是小菜不拖延博主,本篇是背包问题的第二篇,主要基于acwing再重温一边完全背包问题,希望能够帮到你~
完全背包问题
完全背包问题在01背包(再来一遍背包问题 | 01背包)的基础上不同的是,每个物品可以被选多次
原题链接:3. 完全背包问题 - AcWing题库
基本逻辑代码
也就是每个物品可以被选k次,如果理解了01背包的话,其实这里也就是又要多一层循环,每个物品被选k次 (基本逻辑现在是会超时的)
c++
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N], w[N];
int f[N][N];
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=0;j<=m;j++)
{
//每次的k件总体积要小于当前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]);
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
js
function knapsack(n, m, items) {
let f = Array(n+1).fill().map(() => Array(m+1).fill(0));
for (let i = 1; i <= n; i++) {
for (let j = 0; j<=m; j++) {
for(let k=0;k*v[i]<=j;k++){
f[i][j] = Math.max(f[i][j], f[i-1][j - items[i][0]] + items[i][1]);
}
}
}
return f[n-1][m];
}
// 示例输入
let n = 4;
let m = 3;
let items = [
[0,0],
[1, 2], // 物品1: 重量 1,价值 2
[2, 4], // 物品2: 重量 2,价值 4
[2, 5], // 物品3: 重量 3,价值 4
[4, 5] // 物品4: 重量 4,价值 5
];
console.log(knapsack(n, m, items)); // 输出最大价值
疑难解释
为什么不再像01背包一样判断是否需要取当前物品了
因为我们加入了参数k,当k等于0的时候就相当于取前i-1个物品,而且我们已经在循环条件中加入了体积判断,就不需要再进行一次判断了,相当于这里进行了小小的优化
k循环优化
c++
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N], w[N];
int f[N][N];
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=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;
}
js
function knapsack(n, m, items) {
let f = Array(n+1).fill().map(() => Array(m+1).fill(0));
for (let i = 1; i <= n; i++) {
for (let j = 0; j<=m; j++) {
//不选第i个物品
f[i][j]=f[i-1][j]
//选第i个物品,注意这里代码和01不一样
if(j>=items[i][0]) f[i][j] = Math.max(f[i-1][j], f[i][j - items[i][0]] + items[i][1]);
}
}
return f[n-1][m];
}
// 示例输入
let n = 4;
let m = 5;
let items = [
[0,0],
[1, 2], // 物品1: 重量 1,价值 2
[2, 4], // 物品2: 重量 2,价值 4
[3, 4], // 物品3: 重量 3,价值 4
[4, 5] // 物品4: 重量 4,价值 5
];
console.log(knapsack(n, m, items)); // 输出最大价值
注意
和01背包代码不一样!!!,看过01背包的观众老爷会发现,欸,这代码不就是01背包吗。事实上确实相似99.99%,但是恶魔往往藏在细节之中
- 01背包:
f[i][j] = Math.max(f[i-1][j], f[i-1][j - items[i][0]] + items[i][1])
;- 完全背包:
f[i][j] = Math.max(f[i-1][j], f[i][j - items[i][0]] + items[i][1])
;可以看到一个是i-1一个是i。在01背包文章中我们特意强调过这个下标问题,如果是i,那就是基于i,就等于i已经被选过了,恰巧,我们完全背包的情况还真就是i个物品能被选多次(巧了吗这不),所以上一节容易误导的地方恰巧就是完全背包的解法,等于你掌握了01背包就掌握了完全背包!!
解释
如下图,y总写的式子。
第一个式子:当k=1,2,3···时,f[i][j]在选k个i物品的最大值
第二个式子:当k=1,2,3···时,f[i][j-v]在选k个i物品的最大值(也就是把第一个式子k=1时提出来看他的最大值从何而来)
我们会发现每次计算好像都有重复的部分,上下两个式子就差一个w,max(a+w,b+w)==max(a,b)+w,所以第二个式子两边同事加上w,第一二个式子右边黄色部分就相同了,所以替换成第二个式子的左边部分,我们就得到了最终优化后的式子 所以就等于我们不用每次都循环k,只要比上一次多一个w就好了
二维优化成一维
既然01背包能优化到一维,完全背包也一样(经过上面的对比,你是否猜到了代码呢!) 显然完全背包的下标都是i,那我们就可以轻松的直接化成一维了,不用像01背包那样因为下标不同产生额外的变化
c++
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N], w[N];
int f[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++)
{
//小于v[i]装不下第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;
}
js
function knapsack(n, m, items) {
let f = Array(m + 1).fill(0);
for (let i = 1; i <= n; i++) {
let [weight, value] = items[i];
for (let j = items[i][0]; j >=weight; j--) {
f[j] = Math.max(f[j], f[j - weight] + value);
console.log(f,j,weight)
}
}
return f[m];
}
// 示例输入
let n = 4;
let m = 5;
let items = [
[0,0],
[1, 2], // 物品1: 重量 1,价值 2
[2, 4], // 物品2: 重量 2,价值 4
[3, 4], // 物品3: 重量 3,价值 4
[4, 5] // 物品4: 重量 4,价值 5
];
console.log(knapsack(n, m, items)); // 输出最大价值