01背包
01背包问题 :
现在有一个背包 背包容量为 m 还有n件物品,每件物品有自己的所占的容量 v 与价值 w ,每件物品只能取一次或者不取,求在背包容量为m时可取的最大价值
贪心的问题
在新手不知道01背包的话可能会实现一个这样的贪心 用 w/v 把性价比,进行降序排,然后顺着取,但是如果说现在这样
背包容量为 5 有 3件物品 第一件物品大小为 2 价值为 2
物品编号 | 容量(v) | 价值(w) |
1 | 2 | 3 |
2 | 3 | 4 |
3 | 4 | 6 |
这样按照 w/v 性价比 算的话 先装性价比高的 能获取的最大价值为 6 ,但实际上这个背包能获取的最大价值时7。
上面是个典型的贪心,但是贪心算法总是一种局部最优解,而我们需要全局最优解
全局最优 -动态规划
下面是给出01背包问题的
现在背包容量为 7 有 4件物品
物品编号 | 容量 | 价值 | |
1 | 2 | 2 | |
2 | 3 | 4 | |
3 | 4 | 5 | |
4 | 6 | 7 |
现在,我们这样,假如背包容量为 1 对 前1件物品进行取舍,每件物品,只有两种可能,拿取或者不拿 ,那么我们现在设置一个dp数组
物品编号(看下方)|背包容量(看右边) | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1(2,2) | 0 | 2 | 2 | 2 | 2 | 2 | 2 |
2(3,4) | |||||||
3(4,5) | |||||||
4 (6 , 7) |
物品编号(看下方)|背包容量(看右边) | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1(2,2) | 0 | 2 | 2 | 2 | 2 | 2 | 2 |
2(3,4) | 0 | 2 | 4 | 4 | 6 | 6 | 6 |
3(4,5) | |||||||
4 (6 , 7) |
我们对背包容量进行枚举,每一次判断这个物品是否可以放入,如果可以就对前i-1件物品,容量为j-v[i]是进行考量,达到每一个dp元素都是当前的最优解,达到全局最优的。
二维01背包板子
那么我们创建二维的dp数组,给出板子
#include<bits/stdc++.h>
using namespace std;
int n, m;//n 是物品数量 ,m是背包大小
int v[1010], w[1010];// v是物品体积,w是物品价值
int dp[1010][1010];
signed 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++)
{
if (j >= v[i])//背包大小为j时,需要考虑此时放入第i个物品,当放入第i个物品时,此时背包体积为j时的最优解变成前i-1件物品在背包容量为j-v[i]时的最优解
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
else
dp[i][j] = dp[i - 1][j]; //第i件物品大于背包大小为j时,放不下,此时全局最优解时 背包大小为j,前i-1件物品时的最优解
}
}
cout << dp[n][m];
return 0;
}
当然这里还可以优化点空间,把dp数组压缩成一维数组,节省一点买不起的空间
优化一维
在上面状态转移方程时
dp[i][j]=max(dp[i-1][j],d[i][j-v[i]]+w[i]);
如果说我们省略 i 也就是 件数 ,那么 状态转移方程该咋写呢
不讲废话 直接看板子
#include<bits/stdc++.h>
using namespace std;
int n, m;//n 是物品数量 ,m是背包大小
int v[1010], w[1010];// v是物品体积,w是物品价值
int dp[1010];
signed 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 = m; j >= v[i]; j--)
{
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[n];
return 0;
}
完全背包
完全背包问题
现在有一个背包 背包容量为 m 还有n件物品,每件物品有自己的所占的容量 v 与价值 w ,每件物品能取任意次或者不取,求在背包容量为m时可取的最大价值。
问题探究
一个物品取得次数已经给出是无数次,我们不需要考虑这当前的物品(第i件)时对前面 前 i-1 件进行取舍 ,比如说现在正在取第i件物品,这件物品的容量为v[i],价值为 w[i] ,那么在容器的容积为x时 ,如果我们考虑放1件这个物品,那嘛,现在情况变成 在容积为 x-v[i]时对前i件物品的取舍,
也就是说 dp[i][x]=max(dp[i][x],dp[i][x-v[i]+w[i]) 这个状态转移,我们在当前容积对前第i件物品,考虑时放几个 ,如果放1个就是 dp[i][x]=max(dp[i][x],dp[i][x-v[i]]+w[i]) ,如果放两个
dp[i][x]=max(dp[i][x],dp[i][x-2*v[i]]+2*w[i]) 当然前提是能放的下,相比于 01 背包,我们由于每次取舍,如果取就得变成在前i-1物品背包容量为x-v[i]。大家理解下这段话
j接下来时代码
#include<bits/stdc++.h>
using namespace std;
int n, m;//n 是物品数量 ,m是背包大小
int v[1010], w[1010];// v是物品体积,w是物品价值
int dp[1010][1010];
signed 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++)
{
for (int k = 1; k * v[i] <= j; k++) //k 是当前放入得个数 ,如果种物品放入的 个数*每个的容积<=当前背包的容积
{
dp[i][j] = max(dp[i][j], dp[i][j - k * v[i]] + k * w[i]);
}
}
}
cout << dp[n][m];
return 0;
}
对比01背包的优化
当然这个也可以节省一点价值不菲的空间,同01背包,也是舍去当前的物品种类数。
现在我们从新回头看之前对01背包的优化
那么由上面对我们从前面往后面遍历即使完全背包,蒙b了吗(咳咳)
代码
#include<bits/stdc++.h>
using namespace std;
int n, m;//n 是物品数量 ,m是背包大小
int v[1010], w[1010];// v是物品体积,w是物品价值
int dp[1010];
signed 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++)
{
for (int k = 1; k * v[i] <= j; k++) //k 是当前放入得个数 ,如果种物品放入的 个数*每个的容积<=当前背包的容积
{
dp[j] = max(dp[j], dp[j - k * v[i]] + k * w[i]);
}
}
}
cout << dp[m];
return 0;
}