目录
2、基础版本1_题解代码核心算法(只能提供思路,三维枚举太大):
一、01背包问题(物品仅可选一次)
1、题目:
有 NN 件物品和一个容量是 VV 的背包。每件物品只能使用一次。
第 ii 件物品的体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
2、版本1_题解代码
①代码:
#include<iostream>
using namespace std;
const int maxi=1005;
int v[maxi];
int w[maxi];
int f[maxi][maxi];
int main()
{
int N,V;
cin>>N>>V;
for(int i=1;i<=N;i++)
cin>>v[i]>>w[i];
for(int i=1;i<=N;i++)
for(int j=1;j<=V;j++)
{
//算法1:
//if(j<v[i]) //当剩余容量j能装下时,j状态下的最大价值为f[i-1][j];
// f[i][j]=f[i-1][j];
//else //当剩余容量j能装下时,取 装与不装 两情况下的 最大值;
// f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
//算法2:(推荐)
f[i][j]=f[i-1][j]; //选择本轮(i)不取,即上轮(i-1)的值赋给本轮(i);
if(v[i]<j)
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]); //然后来比较 不取和取 谁更大;
}
cout<<f[N][V]<<endl;
return 0;
}
②详解:
(1)状态f[i][j]定义:前 i 个物品,背包容量 j 下的最大价值(最优解):
当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,有 N 件物品,
则需要 N 次决 策,每一次对第 i 件物品的决策,状态f[i][j]不断由之前的状态更新而来。
(2)当前背包容量不够(j < v[i]),没得选,因此前 i个物品最优解即为前 i−1个物品最优
解,其代码:f[i][j] = f[i - 1][j]
(3)当前背包容量够,可以选,因此需要决策选与不选第 ii 个物品:
选:f[i][j] = f[i - 1][j - v[i]] + w[i]。
不选:f[i][j] = f[i - 1][j] 。
最后,用max() 取最大价值。
//上述(1)(2)(3)主要用来理解:
(4)更推荐的算法写法(其实类似)!!!
//算法2:(推荐)
f[i][j]=f[i-1][j]; //选择本轮(i)不取,即上轮(i-1)的值赋给本轮(i);
if(v[i]<j)
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]); //然后来比较 不取和取 谁更大;
3、版本2_题解代码(优化为一维)
①代码:
#include <iostream>
using namespace std;
const int amax=1005;
int v[amax];
int w[amax];
int f[amax];
int main()
{
int N,V;
cin>>N>>V;
for(int i=1;i<=N;i++)
cin>>v[i]>>w[i];
for(int i=0;i<=N;i++)
for(int j=V;j>=v[i];j--)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[V]<<endl;
return 0;
}
②详细:
将状态f[i][j]优化到一维f[j],实际上只需要做一个等价变形。
可以这样变形的原因:我们定义的状态f[i][j]可以求得任意合法的i 与j最优解,但题目只需求得最终状态f[N][V],因此我们只需要用一维空间来更新状态。
(1)状态f[j]定义:N 件物品,背包容量j下的最优解。
(2)注意枚举背包容量j必须从m开始。
一维情况下枚举背包容量需要逆序的原因:在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。
例如,一维状态第i轮对体积为 3 的物品进行决策,则f[7]由f[4]更新而来,这里的f[4]正确应该是f[i - 1][4],但从小到大枚举j这里的f[4]在第i轮计算却变成了f[i][4]。当逆序枚举背包容量j时,我们求f[7]同样由f[4]更新,但由于是逆序,这里的f[4]还没有在第i轮计算,所以此时实际计算的f[4]仍然是f[i - 1][4]。
简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被污染,但逆序则不会有这样的问题。
状态转移方程为:f[j] = max(f[j], f[j - v[i]] + w[i] )
4、版本3_题解代码(优化输入)(优选!!!)
①代码:
#include <iostream>
using namespace std;
const int imax=1005;
int f[imax];
int main()
{
int N,V;
cin>>N>>V;
for(int i=1;i<=N;i++)
{
int v,w;
cin>>v>>w;
for(int j=V;j>=v;j--)
f[j]=max(f[j],f[j-v]+w);
}
cout<<f[V]<<endl;
return 0;
}
②详解:
我们注意到在处理数据时,我们是一个物品一个物品,一个体积一个体积的枚举。
所以我们可以不需要用两个数组记录体积和价值,而是边输入边处理。
二、完全背包问题(物品可选无限次)
1、题目:
有 N 种物品和一个容量是 V 的背包,每种物品可选无限次。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
2、基础版本1_题解代码核心算法(只能提供思路,三维枚举太大):
核心算法:
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-k*v[i]]+k*w[i]);
}
3、版本2_题解代码(算法优化-消k)
①代码:
#include <iostream>
using namespace std;
int const imax=1005;
int v[imax];
int w[imax];
int f[imax][imax];
int main()
{
int N,V;
cin>>N>>V;
for(int i=1;i<=N;i++)
cin>>v[i]>>w[i];
for(int i=1;i<=N;i++)
for(int j=0;j<=V;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]);
}
cout<<f[N][V]<<endl;
return 0;
}
②详解:
来看看列举出来的更新次序的内部关系:
{
①式:f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2*v]+w+w , f[i-1,j-3*v]+w+w+w , .....)
②式:f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-3*v]+w+w , .....)
③式:f[i,j-v-v]=......
}
由上①②两式,可得出如下递推关系:
f[i][j]=max( f[i-1][j],f[i][j-v]+w)
有了上面的关系,那么k循环就可以消去了,核心代码即可优化为下列代码:
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]>=0)
f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
}
4、版本3_题解代码(同01背包类似的一维化)
①代码:
#include <iostream>
using namespace std;
int const imax=1005;
int v[imax];
int w[imax];
int f[imax];
int main()
{
int N,V;
cin>>N>>V;
for(int i=1;i<=N;i++)
cin>>v[i]>>w[i];
for(int i=1;i<=N;i++)
for(int j=v[i];j<=V;j++)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[V]<<endl;
return 0;
}
②详解:
两个代码其实只有一处不同(注意数组下标)
01背包:
f[i][j]=f[i-1][j]
if(v[i]<j)
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
完全背包问题:
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]);
因为和01背包代码类似,所以很容易想到进一步优化,核心代码可改为
for(int i = 1 ; i<=n ;i++)
for(int j = v[i] ; j<=m ;j++) //注意!由于物品无限用,前轮用过后轮还可能要用,所以必须需要 正序枚举,这01背包的(怕污染)必须逆序枚举不同。
{
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
5、版本4_题解代码(一维+输入优化)(优选!!!)
①代码:
#include<iostream>
using namespace std;
const int imax = 1005;
int f[imax];
int main()
{
int N,V;
cin>>N>>V;
for(int i = 1 ; i<=N ;i++)
{
int v,w;
cin>>v>>w;
for(int j = v ; j<=V ;j++)
f[j] = max(f[j],f[j-v]+w);
}
cout<<f[V]<<endl;
return 0;
}
②详解:
同01问题一样
三、01(只能选一次)背包和完全(可选多次)背包总结:
1、v和j的大小比较时机
①01背包和完全背包都要注意:使用不同代码版本时,如果循环中有所需容量v和容量j的关系,则之后不再需要比较v[i]和 j 后判断。
②不需要在循环时,但需要在循环后,比较v[和j的情况:
01背包二维数组算法、完全背包消K优化算法;
③需要在循环中,比较v和j的情况:
01背包一维数组优化算法、{01背包一维数组且输入优化算法}、完全背包含K算法、完全背包消K化一维数组优化算法、{完全背包消K化一维数组且输入优化算法};
④小结:使用一维数组算法和含K的二维数组算法循环时比较v和j,消K的二维数组算法和二维数组算法循环后比较v和j
2、01背包和完全背包一维数组算法的正逆序问题
①首先来看两者 二维数组核心 核心算法的不同:
//01背包
f[i][j]=f[i-1][j]
if(v[i]<j)
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
//完全背包
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]);
②不难看出01背包问题在面临抉择时,需要逆向地向前取“i-1”情况下的值;所以如果一维数组条件下正序枚举,则结果输出值可能获取了被污染的前面的值,所以01背包一维数组算法必须逆序枚举!
③而完全背包问题在面临抉择时,由于每个物品可以取任意多次,即前面轮次放过的物品,在后面轮次可能还需要再放,所以必须!正序枚举!
3、完全背包问题_一维数组过程模拟:
首先,dp[V]数组初始化全为0:给定物品种类为4种,背包的最大容积为5单位,数据犹题目的输入:
v[1] = 1, w[1] = 2
v[2] = 2, w[2] = 4
v[3] = 3, w[3] = 4
v[4] = 4, w[4] = 5
i = 1 时: j从v[1]到5
dp[1] = max(dp[1],dp[0]+w[1]) = w[1] = 2 (用了一件物品1)
dp[2] = max(dp[2],dp[1]+w[1]) = w[1] + w[1] = 4(用了两件物品1)
dp[3] = max(dp[3],dp[2]+w[1]) = w[1] + w[1] + w[1] = 6(用了三件物品1)
dp[4] = max(dp[4],dp[3]+w[1]) = w[1] + w[1] + w[1] + w[1] = 8(用了四件物品1)
dp[5] = max(dp[3],dp[2]+w[1]) = w[1] + w[1] + w[1] + w[1] + w[1] = 10(用了五件物品)
i = 2 时:j从v[2]到5
dp[2] = max(dp[2],dp[0]+w[2]) = w[1] + w[1] = w[2] = 4(用了两件物品1或者一件物品2)
dp[3] = max(dp[3],dp[1]+w[2]) = 3 * w[1] = w[1] + w[2] = 6(用了三件物品1,或者一件物品1和一件物品2)
dp[4] = max(dp[4],dp[2]+w[2]) = 4 * w[1] = dp[2] + w[2] = 8(用了四件物品1或者,两件物品1和一件物品2或两件物品2)
dp[5] = max(dp[5],dp[3]+w[2]) = 5 * w[1] = dp[3] + w[2] = 10(用了五件物品1或者,三件物品1和一件物品2或一件物品1和两件物品2)
i = 3时:j从v[3]到5
dp[3] = max(dp[3],dp[0]+w[3]) = dp[3] = 6 // 保持第二轮的状态
dp[4] = max(dp[4],dp[1]+w[3]) = dp[4] = 8 // 保持第二轮的状态
dp[5] = max(dp[5],dp[2]+w[3]) = dp[4] = 10 // 保持第二轮的状态
i = 4时:j从v[4]到5
dp[4] = max(dp[4],dp[0]+w[4]) = dp[4] = 10 // 保持第三轮的状态
dp[5] = max(dp[5],dp[1]+w[4]) = dp[5] = 10 // 保持第三轮的状态
由模拟的完全背包的全部过程,也可以看出,最后一轮的dp[V]即为最终的返回结果。