背包问题——3. 完全背包

完全背包问题

#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)

using namespace std;

const int maxn=1010,maxv=1010;
int v[maxn],w[maxn];
int dp[maxv];

int main() {
    
    
    int N,V;
    read(N,V);
    for (int i=1;i<=N;i++) read(v[i],w[i]);

    for (int i=1;i<=N;i++)
        for (int j=v[i];j<=V;j++)
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
         
    printf("%d",dp[V]);

    return 0;
}

题目描述

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10

分析:

简单01背包中是从N个物品里选,每个物品只能用1次,完全背包则不同,每个物品可以用无限次。

所以01背包里每个物品只能是选(1)或者不选(0)两种状态,而完全背包里,假设背包体积为V,对于某个体积为v的物品可以有k+1种状态,其中满足k*v<=V,对应着选该物品0、1、2……k次。

01背包:
如果物品能放入背包,则动态转移方程为 dp[i][j]=max( dp[i-1][j],dp[i-1][ j-v[i] ]+ w[i] ), j>=w[i]
如果物品不能放入背包,dp[i][j]=dp[i-1][j] ,j<w[i]

完全背包:
动态转移方程满足:dp[i][j]=max{dp[i-1][ j-k×v[i] ] + k×w[i]},其中 0 ≤ k×w[i] ≤ j
可以发现,当k只能取0、1时的特例就是简单的0-1背包问题。

可以发现,选0次,即不选是肯定存在的,dp[i][j]=dp[i-1][j];
然后选1次,那就不一定存在了,可能可以选,也可能不可以选,假设可以选,在选0次该物品求出dp[i][j]的基础上,确定是否选该物品:if (j>=v[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
选2次、选3次,依次类推,一直到选k次。

上面这种也就是简单01背包中二维数组实现的写法,即手动模拟for循环,循环两次,不过完全背包中是循环k次,(k>1),但是对于只有两种选择的01背包,也可以用if、else判断,见滚动数组实现。

二维数组实现

#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)

using namespace std;

const int maxn=1010,maxv=1010;
int v[maxn],w[maxn];
int dp[maxn][maxv];//N行V列,0行0列初始为0

int main() {
    
    
    int N,V;
    read(N,V);
    for (int i=1;i<=N;i++) read(v[i],w[i]);

    for (int i=1;i<=N;i++)
        for (int j=0;j<=V;j++) "已经初始化第0列为0了,所以不管j从1还是从0开始,都一样"
            for (int k=0;k*v[i]<=j;k++)
                dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);

    printf("%d",dp[N][V]);

    return 0;
}

上面第二重循环,j从0开始时,由于物品体积都大于0,继续循环的条件k*v[i]<=j,所以当j=0时,只会循环一次,即不选择物品时,dp[i][0]=dp[i-1][0],最终等于dp[0][0]=0,所以当初始化第0列以后,j从1开始就行。

二重循环里的语句三重循环的循环k次可以类比简单01背包的二重循环里面的语句,01背包是循环两次的简便写法,完全背包则是循环k次,不过为了dp[i][j]第一次能够取到dp[i-1][j],应该给dp[i][j]先初始化一个小于等于0的值,因为最后得到的dp二维表里,值最小值就是0。

时间复杂度优化

上面要经过三重循环,时间复杂度较高,我们可以将 f[i, j] = max { f[i-1, j-k*v[i]] + k*w[i] } 展开,就可以发现如下规律:

假设第i个物品体积为v,价值为w,最多能将k个该物品装进体积为j的背包,即k*v<=j

对于选择前i个物品,体积为j的背包:,最多可以装入k个该物品,

f [ i , j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v ] + w , f [ i − 1 ] [ j − 2 v ] + 2 w … … f [ i − 1 ] [ j − k v ] + k w ) f[i,j]=max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w……f[i-1][j-kv]+kw) f[i,j]=max(f[i1][j]f[i1][jv]+wf[i1][j2v]+2wf[i1][jkv]+kw)

对于选择前i个物品,体积为j-v的背包:,最多可以装入k-1个该物品,
f [ i , j − v ] = m a x ( f [ i − 1 ] [ j − v ] , f [ i − 1 ] [ j − 2 v ] + w … … f [ i − 1 ] [ j − k v ] + ( k − 1 ) w ) f[i,j-v]=max(f[i-1][j-v],f[i-1][j-2v]+w……f[i-1][j-kv]+(k-1)w) f[i,jv]=max(f[i1][jv]f[i1][j2v]+wf[i1][jkv]+(k1)w)

二者进行等量代换,可以得到优化后的完全背包的状态转移方程f[i][j]=max(f[i-1][j],f[i][j-v]+w)
对比简单01背包的状态转移方程:f[i][j]=max(f[i-1][j],f[i-1][j-v]+w)

代码实现

#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)

using namespace std;

const int maxn=1010,maxv=1010;
int v[maxn],w[maxn];
int dp[maxn][maxv];//N行V列,0行0列初始为0

int main() {
    
    
    int N,V;
    read(N,V);
    for (int i=1;i<=N;i++) read(v[i],w[i]);

    for (int i=1;i<=N;i++)
        for (int j=0;j<=V;j++) {
    
    
            dp[i][j]=dp[i-1][j];
            if (j>=v[i]) dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
        }
    printf("%d",dp[N][V]);

    return 0;
}

仅有一处不同,可以看出简单01背包的第i行的dp[i][j]完全取决于第i-1行的dp[i-1][0]到dp[i-1][j]的数据,
而完全背包第i行的dp[i][j]则取决于第i-1行的一个数据dp[i-1][j]以及第i行本身的dp[i][0]到dp[i][j-1]的数据。

优化+一维数组实现

完全背包中第i行前几列更新的数据需要被后几列用到,所以像简单背包那样进行一维数组优化的时候,二重循环就不能倒序循环了,要正序循环了。

可以理解为给体积为j的背包中选取k个某物品也是一个递推的过程,选取k个该物品就去选取k-1个该物品的时候找答案,一直往下找。

而01背包中若是也正序更新,第i-1行的数据就会被第i行新更新的数据覆盖,但后面用到的是第i-1行的数据,为了避免数据被破坏,所以倒序更新。

#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)

using namespace std;

const int maxn=1010,maxv=1010;
int v[maxn],w[maxn];
int dp[maxv];

int main() {
    
    
    int N,V;
    read(N,V);
    for (int i=1;i<=N;i++) read(v[i],w[i]);

    for (int i=1;i<=N;i++)
        for (int j=v[i];j<=V;j++)
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
         
    printf("%d",dp[V]);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/HangHug_L/article/details/114238728
今日推荐