完全背包问题描述
有一个最多可以装质量为W的背包,有N件物品,每件物品都有无数件,第i件物品的质量为w [ i ] 价值为 v[ i ]。
问:在不超过背包容量下,可以获得的最大价值是多少?
(注:如果不会01背包的请大家先阅读下蒟蒻写的01背包,会了01背包,其他的背包问题也就很简单啦~)
01背包详解网址:01背包详解
方法一:直接扩展为01背包问题
请大家跟我一起回想一下01背包问题吧!01背包问题和完全背包不同的地方就是:完全背包的限制条件是每个物品都有无数件,01背包是没件物品仅仅可以拿一件或者选者不拿,那么问题的关键来了,咱们只需要把01背包的转移方程**添加每个物品尽可能多的拿**这一条件,不就解决了完全背包问题了吗?
给大家看一下把01背包扩展成完全背包的递推方程:
dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - w[i]] + v[i]); //01背包问题的递推方程式
dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - k * w[i]] + K * v[i]); //完全背包问题的递推方程式
//dp[i][w] 代表前i件物品放入质量为w的背包时的最大价值。
//k 代表着第i件物品拿了几件,咱们枚举一下自然就知道几件的时候可以使得价值最大,这个就是扩展01背包问题的关键地方
给大家一个完整的完全背包的代码吧~
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int W, N, dp[100][1200], w[100], v[100];
//由于没说必须装满背包,所以初始化的时候都为0就可以,至于为什么,请大家看下我的那篇01背包的博客,都有清晰的讲解
int main()
{
scanf("%d %d", &W, &N);
for(int i = 1; i <= N; i++)
scanf("%d %d", &w[i], &v[i]);
for(int i = 1; i <= N; i++){
for(int j = 1; j <= W; j++){
for(int k = 1; j - k*w[i] >= 0; k++){ //防止越界
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - k * w[i]] + k * v[i]); //完全背包问题的递推方程式
//dp[i][w] 代表前i件物品放入质量为w的背包时的最大价值。
//k 代表着第i件物品拿了几件,咱们枚举一下自然就知道几件的时候可以使得价值最大,这个就是扩展01背包问题的关键地方
}
}
}
printf("%d", dp[N][W]);
return 0;
}
对于完全背包问题还有一个简单又有效的优化,那就是如果 w[a] > w[b] && v[a] < v[b] 这种情况下就可以a物品去掉,因为有b就没必要去选a了,因为a比b重而且a的价值比b还小。
方法二:转化为01背包问题
这种方法有两个思路来进行转化:
第一种思路:
完全背包是每件物品可以取无数件,但是呢这个物品再多件,由于背包的可承受的重量的限制,该物品也会有他的最多可拿件数。
第i件物品的可拿件数:N_MAX = W / w[i] --: W 是该背包可以承受的总重量,w[i]代表第i件物品的重量。
所以呢咱们可以把题目扩展一下,来用01背包解决,怎么扩展呢,比如说全部装第i件物品,最多可以装 j 件,那么就把这个物品存三次,也就是说,一个w【i】v【i】 有 j 个重复的
举个荔枝:
物品的总个数 N=3 背包最多可承受的重量为: W = 5
第i件物品 w(重量) v(价值)
1 3 5
2 2 10
3 2 20
物品扩展为:
物品的总个数 N=3 背包最多可承受的重量为: W = 5
第i件物品 w(重量) v(价值)
1 3 5
2 2 10
3 2 10
4 2 20
5 2 20
由于重量为 2 价值为10的物品,最多也就可以装下2件,所以扩展成两个一样的该物品,每一件物品都进行这样的处理,之后问题就变成了01背包问题,直接用01背包的算法就可以解决。
第二种思路:
大家应该有学过快速幂的把,在这为了一会更好的理解,插入一点快速幂的介绍:
假设我们要求a^b,那么其实b是可以拆成二进制的,该二进制数第i位的权为2^(i-1),例如当b==11时:
a^11 = a^(2^0 + 2^1 + 2^3) = a^0 * a^2 * a^8 这样原本应该算11次的,这样转换之后就变成了算3次。
而咱们的这种思路于之有异曲同工之妙。
具体方法:把第i种物品拆成质量为w[i]*2^k、价值为v[i]*2^k的若干件物品,其中k满足w[i]*2^k<=W。
这个二进制的思想,就是不管什么数都可以构成若干个2^k的和,比如刚才说的那个快速幂的荔枝,11就可以看成2^0 + 2^1 + 2^3加和,因为11的二进制为:1011 == 2^0 + 2^1 + 2^3 ,所以呢咱们的物品没必要非得拆成一件一件的呦,那么咱们怎么拆呢?
举个荔枝:
物品的总个数 N=3 背包最多可承受的重量为: W = 5
第i件物品 w(重量) v(价值)
1 3 5
2 1 10
3 2 20
物品拆分为:
物品的总个数 N=3 背包最多可承受的重量为: W = 5
第i件物品 w(重量) v(价值)
1 3 5
2 1 10
3 2 20
4 4 40
5 2 20
6 4 40
大家看第2件物品质量为1 所以呢,可以背包里5件全部装它,那么咱们就扩展了下。扩展成了可以装1 件 2件 4件,这些可以组合成任意的 1- 5的数,也就是可以往背包里放1 - 5 个数量i物品。这样的话,时间复杂度会大大的下降+
方法三:直接建立完全背包的递推关系式
01背包的递推关系式:
dp[i][j] = max( dp[i-1][j], dp[i - 1][j-w[i]] + v[i] )
完全背包的递推关系式:
dp[i][j] = max( dp[i-1][j], dp[i][j-w[i]] + v[i] )
在完全背包中,完全背包的特点是每种物品可选无限件,在求解加选第 i 种物品带来的收益dp[i][j]时,在状态dp[ i ][ v - c[ i ] ]中已经尽可能多的放入物品i了,此时在dp[i][v-c[i]]的基础上,我们可以再次放入一件物品i,此时也是在不超过背包容量的基础下,尽可能多的放入物品i。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int W, N, dp[100][1200], w[100], v[100];
//由于没说必须装满背包,所以初始化的时候都为0就可以,至于为什么,请大家看下我的那篇01背包的博客,都有清晰的讲解
int main()
{
scanf("%d %d", &W, &N);
for(int i = 1; i <= N; i++)
scanf("%d %d", &w[i], &v[i]);
for(int i = 1; i <= N; i++){
for(int j = 1; 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", dp[N][W]);
return 0;
}
空间复杂度的优化
咱们先上代码!
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]);//背包重量为j时最大价值
}
}
看下代码有木有觉得很熟悉呢!
看过我的01背包的一定知道这个和01背包的压缩空间的代码这不一样吗?!博主不要糊弄人呀!
哈哈~ 确实差不多,但是呢细心想一想,当时01背包的空间压缩优化的时候,我强调了一点就是在第3行的代码,重量应该从最大的地方进行枚举,否者可能会影响到后面的dp[ j ](保证每个物品只能选一件),而这次由于每个物品有无数件,那么咱们就从可以装下第i件物品的重量开始,尽可能的让他装第i件物品,装几遍同一个物品都不怕辣!
(第 i 行的dp[ j ] 代表重量为背包最大承重为j时候的尽可能的装第 i 件物品的最大价值)
参考代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int W, N, dp[1200], w[100], v[100];
//由于没说必须装满背包,所以初始化的时候都为0就可以,至于为什么,请大家看下我的那篇01背包的博客,都有清晰的讲解
int main()
{
scanf("%d %d", &W, &N);
for(int i = 1; i <= N; i++)
scanf("%d %d", &w[i], &v[i]);
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]);//背包重量为j时最大价值
}
}
printf("%d", dp[W]);
return 0;
}
(代码都已验证过,请大家放心去理解它~)