完全背包详解

完全背包问题描述

有一个最多可以装质量为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 = w[i]; j <= W; j++){
                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[W - 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;
}

(代码都已验证过,请大家放心去理解它~)

猜你喜欢

转载自blog.csdn.net/ltrbless/article/details/81565310