问题背景
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
朴素版(会的直接跳过就可以了)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N =1003;
int dp[N][N];
int v[N],w[N];//v -> values w -> weight 习惯这么看
int main()
{
int n,m;
cin>>n>>m;
for(int i = 1;i<=n;i++)
{
cin>>w[i]>>v[i];
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
dp[i][j] = dp[i-1][j];
if(j>=w[i])
{
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]] + v[i]);
}
}
}
cout<<dp[n][m];
return 0;
}
考虑优化
(1)动态规划的思想无非就是由一个状态组成的集合转移到另一个状态集合最后求最终状态的过程,既然知道了这个过程,那么我们能优化的无非是状态转移的过程。(dp[i][j]表示当前考虑第i个背包,且体积为j的最大价值)
(2)0-1背包的状态转移涉及了什么?dp[i][j] 和dp[i-1][j]的状态集合之间的转移,那么是不是只涉及了两个状态。这样,是不是初步可以看出,我们只需要两维空间其中一维只需要开两个就足够了,至于另外一维是背包的容量。
空间优化(但是二维版)
#include <iostream>
using namespace std;
const int N = 1003;
int dp[2][N];
int n,m;
int main()
{
cin>>n>>m;
for(int i = 1;i<=n;i++)
{
int v,w;
cin>>w>>v;
for(int j = 1;j<=m;j++)
{
dp[i&1][j] = dp[i-1&1][j];
if(j>=w)
{
dp[i&1][j] = max(dp[i-1&1][j],dp[i-1&1][j-w] + v);
}
}
}
cout<<dp[n&1][m];
return 0;
}
细节方面:
(1)优化了两个数组,v[i]和w[i],因为考虑第i个物品,它的体积和价值在后面不会再用到,用到的只是背包的最大价值,
(2)为什么是&1就能优化成只用2个,因为&在计算机做运算的是最后一位2进制,然后,随着i的增大,每次二进制表示下的都会+1,故,相邻两个自然数最后一位&1一定不同,因此,在状态转移的时候两个状态能被找到并且能够唯一表示,保证了不需要的数组都会被更新。
(3)不要忘记在求最终状态的时候也要&1,因为最终状态被更新的时候是在for循环i==n的时候被更新,因此,结合之前,当前状态唯一被确定可知,答案就是n&1的那一个。
(4)其实我们还能再细一点,在第一层for循环内,int w,v这一条可以放在外面,有一些编译器会反复编译,这样就会导致时间会慢一点。还有,i++和j++改成++i和++j会更快,还有快读……(快说我真细)
空间优化(但是一维版)
#include <iostream>
using namespace std;
const int N = 1003;
int dp[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i = 1;i<=n;i++)
{
int w,v;
cin>>w>>v;
for(int j = m;j>=w;j--)
{
dp[j] = max(dp[j],dp[j-w]+v);
}
}
cout<<dp[m];
return 0;
}
结合上面的版本,我们还知道,dp[i[[j]这个位置的状态由上面那种状态更新的,无非两种,第一种,被正上方那个,也就是不选当前物品所转移的状态,第二种被左上方的状态转移,因为j-w这个状态是必然小于j的,(选了该物品且该物品体积必然>=1),因此可以从后往前去更新,既保证了需要的状态能够保存,又可以直接在for循环里面直接让j>=w结合选和不选两种情况,因为不选的情况相当于不做处理。