1.什么是背包问题
背包的基本模型:
给你一个容量为V的背包和若干种物品,在一定的限制条件下(每种物品都占用一定容量),问最多能放进多少价值的物品?
2.01背包
题意:有n个重量和价值分别为wi,vi,从这些物品中挑选总重量不超过W的物品,使其价值最大,输出价值最大值(其中每个物品最多只能放一件);
这题其实用dfs也可以做,但超时
①、确认子问题和状态
01背包问题需要求解的就是,为了重量为W的背包中物体总价值最大化,N件物品中第i件应该放入背包中吗?(其中每个物品最多只能放一件);
为此,我们定义一个二维数组,其中每个元素代表一个状态,即前i个物体中若干个放入重量为W背包中最大价值。数组为:dp[N][V],其中dp[i][j]表示前i件中若干个物品放入体积为j的背包中的最大价值。
②、初始状态
dp[i][j] = 0;
转移方程:dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);也就是两种状态,第i个物品放还是不放(也就是前i件中是否加入第i件产品进入体积为j的背包),放则为:dp[i-1][j-w[i]]+v[i]
不放则为:dp[i-1][j]
#include<iostream>
using namespace std;
const int maxn = 1000+10;
int dp[maxn][maxn] = {0};
int w[maxn];
int v[maxn];
/*
因为我需要从上层推下层,如果dp数组下标从0开始会比较麻烦点,所以这里我从1开始
*/
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)
{
//只有>=才会有两种选择,放或不放
if(j >= v[i])
{
dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
//放了则表示为dp[i-1][j-v[i]]+w[i]
}
else
dp[i][j] = dp[i-1][j];
}
}
/*
这里我觉得会给大家一个错觉,认为dp[N][V]就是最大,其实我们应该遍历dp[n][i]得到最大
再输出。这里为何会直接输出dp[N][V],试想dp[i][j]与dp[i][j-1]相比,如果j-1可以放入第i件
,那么j必定可以,所以这里其实已经保持最大了,所以只需要输出dp[N][V]
*/
cout << dp[N][V] << endl;
return 0;
}
当W = 12,n = 5,
v[6] = {0 , 2 , 5 , 3 , 10 , 4}; //价值
w[6] = {0 , 1 , 3 , 2 , 6 , 2}; //重量
此题空间可以优化:大家看看这张表,再看看转移方程:dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);我们可以用滚动数组,因为前i件只取决前i-1件的状态(不取决i-2,i-3之类的),也就无需保存所有的状态,只需保存当前状态的上一层状态,这里注意此时W需要从大到小来遍历,为什么呢?请看
优化后的转移方程:dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
未优化后的转移方程:dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
当W从小到大来,dp[j-w[i]]取到的已不是前i-1的状态而是前i的状态,你可以想想,j从小到大变化是不是把dp[j]给修改,而这里修改是在前i的状态下,当dp[j] = max(dp[j],dp[j-w[i]]+v[i])中dp[j-w[i]]取到的j是已被修改的,相当于二维中的dp[i][j-w[i]]而不是dp[i-1][j-w[i]]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<set>
#include<cmath>
#include<algorithm>
using namespace std;
int dp[1005];
int main()
{
int n;
int w[1005];
int v[1005];
int W;
scanf("%d",&n);
for(int i = 1;i <= n;++i)
scanf("%d%d",&w[i],&v[i]);
scanf("%d",&W);
for(int i = 1;i <= n;++i)
{
for(int j = W;j >= w[i];--j)
{
dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
}
}
printf("%d\n",dp[W]);
return 0;
}
3.完全背包
N件物品,容量位W的背包。第i件物品的重量为wi,价值为vi,每件物品有无数个,求装的最大价值
同样,定义一个二维数组dp[i][j],表示取前i件中的若干件,在容量为j的背包中的最大价值
dp[i][j] = max(dp[i][j],dp[i-1][j-k*w[i]]); (0<=k<=W/wi),这里k是从0开始
显然时间复杂度比较大,所以需要优化,推导如下:
1.dp[i][j] = max(dp[i-1][j-k*w[i]]); k>=0
2.dp[i][j] = max(dp[i-1][j],dp[i-1][j-k*w[i]]); k>=1
3.dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]-k*w[i]]); k>=0
将1和3
1.dp[i][j] = max(dp[i-1][j-k*w[i]]); k>=0
3.dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]-k*w[i]]); k>=0
dp[i-1][j-w[i]-k*w[i]]和dp[i-1][j-k*w[i]]等价,所以把j-w[i]当成j,所以1.dp[i][j] = max(dp[i-1][j-k*w[i]])就变成dp[i][j-w[i]] = max(dp[i-1][j-k*w[i]]),而dp[i-1][j-k*w[i]]可以代替2中的dp[i-1][j-k*w[i]],2转化为dp[i][j] = max(dp[i-1][j],dp[i][j-w[i]])
可能大家被上面的转换转晕了,没事大家可以看我在时间优化里写的注释,相信大家绝对看的懂。
此时还可以优化空间,也是用滚动数组了,只不过此时j不必4从大到小了,因为max(dp[i-1][j],dp[i][j-w[i]]),这里是前i种状态
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1000+10;
int dp[maxn][maxn]={0};
int main()
{
int w[maxn];
int v[maxn];
int W;
int n;
scanf("%d",&n);scanf("%d",&W);
for(int i = 1;i <= n;++i)
scanf("%d%d",&w[i],&v[i]);
for(int i = 1;i <= n;++i)
{
for(int j = 0;j <= W;++j)
{
for(int k = 0;k*w[i] <=j;++k)
{
dp[i][j] = max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
}
}
}
printf("%d\n",dp[n][W]);
return 0;
}
时间优化后:
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int main()
{
int dp[105][105]={0};
int w[105];
int v[105];
int W;
int n;
scanf("%d",&n);
for(int i = 1;i <= n;++i)
scanf("%d%d",&w[i],&v[i]);
scanf("%d",&W);
/*
f[i,j]=max(f[i-1][j],f[i-1][j-w]+v,f[i-1][j-2w]+2v,f[i-1][j-3w]+3v,......)
f[i,j-v] = max( f[i-1][j-w], f[i-1][j-2w]+v ,f[i-1][j-3w]+2v,......)
可以发现f[i-1][j-w]+v,f[i-1][j-2w]+2v,f[i-1][j-3w]+3v,......这一堆等价于f[i,j-w]+v
所以f[i,j]=max(f[i-1][j],f[i,j-w]+v);所以我们可以把k去掉
*/
for(int i = 1;i <= n;++i)
{
for(int j = 0;j <= W;++j)
{
if(j < w[i])
dp[i][j] = dp[i-1][j];
else
dp[i][j] = max(dp[i-1][j],dp[i][j-w[i]]+v[i]);
}
}
printf("%d\n",dp[W]);
return 0;
}
空间优化:
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int main()
{
int dp[105]={0};
int w[105];
int v[105];
int W;
int n;
scanf("%d",&n);
for(int i = 1;i <= n;++i)
scanf("%d%d",&w[i],&v[i]);
scanf("%d",&W);
for(int i = 1;i <= n;++i)
{
for(int j = w[i];j <= W;++j)
{
dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
}
}
printf("%d\n",dp[W]);
return 0;
}